active_thread.rs

   1use crate::context_picker::{ContextPicker, MentionLink};
   2use crate::context_strip::{ContextStrip, ContextStripEvent, SuggestContextKind};
   3use crate::message_editor::{extract_message_creases, insert_message_creases};
   4use crate::ui::{AddedContext, AgentNotification, AgentNotificationEvent, ContextPill};
   5use crate::{AgentPanel, ModelUsageContext};
   6use agent::{
   7    ContextStore, LastRestoreCheckpoint, MessageCrease, MessageId, MessageSegment, TextThreadStore,
   8    Thread, ThreadError, ThreadEvent, ThreadFeedback, ThreadStore, ThreadSummary,
   9    context::{self, AgentContextHandle, RULES_ICON},
  10    thread_store::RulesLoadingError,
  11    tool_use::{PendingToolUseStatus, ToolUse},
  12};
  13use agent_settings::{AgentSettings, NotifyWhenAgentWaiting};
  14use anyhow::Context as _;
  15use assistant_tool::ToolUseStatus;
  16use audio::{Audio, Sound};
  17use collections::{HashMap, HashSet};
  18use editor::actions::{MoveToEnd, MoveUp, Paste};
  19use editor::scroll::Autoscroll;
  20use editor::{
  21    Editor, EditorElement, EditorEvent, EditorSettings, EditorStyle, MultiBuffer, SelectionEffects,
  22};
  23use gpui::{
  24    AbsoluteLength, Animation, AnimationExt, AnyElement, App, ClickEvent, ClipboardEntry,
  25    ClipboardItem, DefiniteLength, EdgesRefinement, Empty, Entity, EventEmitter, Focusable, Hsla,
  26    ListAlignment, ListOffset, ListState, MouseButton, PlatformDisplay, ScrollHandle, Stateful,
  27    StyleRefinement, Subscription, Task, TextStyle, TextStyleRefinement, Transformation,
  28    UnderlineStyle, WeakEntity, WindowHandle, linear_color_stop, linear_gradient, list, percentage,
  29    pulsating_between,
  30};
  31use language::{Buffer, Language, LanguageRegistry};
  32use language_model::{
  33    LanguageModelRequestMessage, LanguageModelToolUseId, MessageContent, Role, StopReason,
  34};
  35use markdown::parser::{CodeBlockKind, CodeBlockMetadata};
  36use markdown::{
  37    HeadingLevelStyles, Markdown, MarkdownElement, MarkdownStyle, ParsedMarkdown, PathWithRange,
  38};
  39use project::{ProjectEntryId, ProjectItem as _};
  40use regex::Regex;
  41use rope::Point;
  42use settings::{Settings as _, SettingsStore, update_settings_file};
  43use std::ffi::OsStr;
  44use std::path::Path;
  45use std::rc::Rc;
  46use std::sync::{Arc, LazyLock};
  47use std::time::Duration;
  48use text::ToPoint;
  49use theme::ThemeSettings;
  50use ui::{
  51    Banner, Disclosure, KeyBinding, PopoverMenuHandle, Scrollbar, ScrollbarState, TextSize,
  52    Tooltip, prelude::*,
  53};
  54use util::ResultExt as _;
  55use util::markdown::MarkdownCodeBlock;
  56use workspace::{CollaboratorId, Workspace};
  57use zed_actions::assistant::OpenRulesLibrary;
  58use zed_llm_client::CompletionIntent;
  59
  60const CODEBLOCK_CONTAINER_GROUP: &str = "codeblock_container";
  61const EDIT_PREVIOUS_MESSAGE_MIN_LINES: usize = 1;
  62const RESPONSE_PADDING_X: Pixels = px(19.);
  63const ASSISTANT_EDITOR_MAX_LINES: usize = 50;
  64
  65static LANG_PREFIX_REGEX: LazyLock<Regex> = LazyLock::new(|| {
  66    Regex::new(r"```([a-zA-Z0-9]{1,5}) ([^\n]*[/\\][^\n]*\.([a-zA-Z0-9]+)(?:#[^\n]*)?)")
  67        .expect("Failed to create LANG_PREFIX_REGEX")
  68});
  69static PATH_CODE_BLOCK_REGEX: LazyLock<Regex> = LazyLock::new(|| {
  70    Regex::new(r"```(\S*[/\\]\S*\.(\w+)\S*)").expect("Failed to create PATH_CODE_BLOCK_REGEX")
  71});
  72
  73pub struct ActiveThread {
  74    context_store: Entity<ContextStore>,
  75    language_registry: Arc<LanguageRegistry>,
  76    thread_store: Entity<ThreadStore>,
  77    text_thread_store: Entity<TextThreadStore>,
  78    thread: Entity<Thread>,
  79    workspace: WeakEntity<Workspace>,
  80    save_thread_task: Option<Task<()>>,
  81    messages: Vec<MessageId>,
  82    list_state: ListState,
  83    scrollbar_state: ScrollbarState,
  84    show_scrollbar: bool,
  85    hide_scrollbar_task: Option<Task<()>>,
  86    rendered_messages_by_id: HashMap<MessageId, RenderedMessage>,
  87    rendered_tool_uses: HashMap<LanguageModelToolUseId, RenderedToolUse>,
  88    editing_message: Option<(MessageId, EditingMessageState)>,
  89    expanded_tool_uses: HashMap<LanguageModelToolUseId, bool>,
  90    expanded_thinking_segments: HashMap<(MessageId, usize), bool>,
  91    expanded_code_blocks: HashMap<(MessageId, usize), bool>,
  92    last_error: Option<ThreadError>,
  93    notifications: Vec<WindowHandle<AgentNotification>>,
  94    copied_code_block_ids: HashSet<(MessageId, usize)>,
  95    _subscriptions: Vec<Subscription>,
  96    notification_subscriptions: HashMap<WindowHandle<AgentNotification>, Vec<Subscription>>,
  97    open_feedback_editors: HashMap<MessageId, Entity<Editor>>,
  98    _load_edited_message_context_task: Option<Task<()>>,
  99}
 100
 101struct RenderedMessage {
 102    language_registry: Arc<LanguageRegistry>,
 103    segments: Vec<RenderedMessageSegment>,
 104}
 105
 106#[derive(Clone)]
 107struct RenderedToolUse {
 108    label: Entity<Markdown>,
 109    input: Entity<Markdown>,
 110    output: Entity<Markdown>,
 111}
 112
 113impl RenderedMessage {
 114    fn from_segments(
 115        segments: &[MessageSegment],
 116        language_registry: Arc<LanguageRegistry>,
 117        cx: &mut App,
 118    ) -> Self {
 119        let mut this = Self {
 120            language_registry,
 121            segments: Vec::with_capacity(segments.len()),
 122        };
 123        for segment in segments {
 124            this.push_segment(segment, cx);
 125        }
 126        this
 127    }
 128
 129    fn append_thinking(&mut self, text: &String, cx: &mut App) {
 130        if let Some(RenderedMessageSegment::Thinking {
 131            content,
 132            scroll_handle,
 133        }) = self.segments.last_mut()
 134        {
 135            content.update(cx, |markdown, cx| {
 136                markdown.append(text, cx);
 137            });
 138            scroll_handle.scroll_to_bottom();
 139        } else {
 140            self.segments.push(RenderedMessageSegment::Thinking {
 141                content: parse_markdown(text.into(), self.language_registry.clone(), cx),
 142                scroll_handle: ScrollHandle::default(),
 143            });
 144        }
 145    }
 146
 147    fn append_text(&mut self, text: &String, cx: &mut App) {
 148        if let Some(RenderedMessageSegment::Text(markdown)) = self.segments.last_mut() {
 149            markdown.update(cx, |markdown, cx| markdown.append(text, cx));
 150        } else {
 151            self.segments
 152                .push(RenderedMessageSegment::Text(parse_markdown(
 153                    SharedString::from(text),
 154                    self.language_registry.clone(),
 155                    cx,
 156                )));
 157        }
 158    }
 159
 160    fn push_segment(&mut self, segment: &MessageSegment, cx: &mut App) {
 161        match segment {
 162            MessageSegment::Thinking { text, .. } => {
 163                self.segments.push(RenderedMessageSegment::Thinking {
 164                    content: parse_markdown(text.into(), self.language_registry.clone(), cx),
 165                    scroll_handle: ScrollHandle::default(),
 166                })
 167            }
 168            MessageSegment::Text(text) => {
 169                self.segments
 170                    .push(RenderedMessageSegment::Text(parse_markdown(
 171                        text.into(),
 172                        self.language_registry.clone(),
 173                        cx,
 174                    )))
 175            }
 176            MessageSegment::RedactedThinking(_) => {}
 177        };
 178    }
 179}
 180
 181enum RenderedMessageSegment {
 182    Thinking {
 183        content: Entity<Markdown>,
 184        scroll_handle: ScrollHandle,
 185    },
 186    Text(Entity<Markdown>),
 187}
 188
 189fn parse_markdown(
 190    text: SharedString,
 191    language_registry: Arc<LanguageRegistry>,
 192    cx: &mut App,
 193) -> Entity<Markdown> {
 194    cx.new(|cx| Markdown::new(text, Some(language_registry), None, cx))
 195}
 196
 197pub(crate) fn default_markdown_style(window: &Window, cx: &App) -> MarkdownStyle {
 198    let theme_settings = ThemeSettings::get_global(cx);
 199    let colors = cx.theme().colors();
 200    let ui_font_size = TextSize::Default.rems(cx);
 201    let buffer_font_size = TextSize::Small.rems(cx);
 202    let mut text_style = window.text_style();
 203    let line_height = buffer_font_size * 1.75;
 204
 205    text_style.refine(&TextStyleRefinement {
 206        font_family: Some(theme_settings.ui_font.family.clone()),
 207        font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
 208        font_features: Some(theme_settings.ui_font.features.clone()),
 209        font_size: Some(ui_font_size.into()),
 210        line_height: Some(line_height.into()),
 211        color: Some(cx.theme().colors().text),
 212        ..Default::default()
 213    });
 214
 215    MarkdownStyle {
 216        base_text_style: text_style.clone(),
 217        syntax: cx.theme().syntax().clone(),
 218        selection_background_color: cx.theme().colors().element_selection_background,
 219        code_block_overflow_x_scroll: true,
 220        table_overflow_x_scroll: true,
 221        heading_level_styles: Some(HeadingLevelStyles {
 222            h1: Some(TextStyleRefinement {
 223                font_size: Some(rems(1.15).into()),
 224                ..Default::default()
 225            }),
 226            h2: Some(TextStyleRefinement {
 227                font_size: Some(rems(1.1).into()),
 228                ..Default::default()
 229            }),
 230            h3: Some(TextStyleRefinement {
 231                font_size: Some(rems(1.05).into()),
 232                ..Default::default()
 233            }),
 234            h4: Some(TextStyleRefinement {
 235                font_size: Some(rems(1.).into()),
 236                ..Default::default()
 237            }),
 238            h5: Some(TextStyleRefinement {
 239                font_size: Some(rems(0.95).into()),
 240                ..Default::default()
 241            }),
 242            h6: Some(TextStyleRefinement {
 243                font_size: Some(rems(0.875).into()),
 244                ..Default::default()
 245            }),
 246        }),
 247        code_block: StyleRefinement {
 248            padding: EdgesRefinement {
 249                top: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
 250                left: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
 251                right: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
 252                bottom: Some(DefiniteLength::Absolute(AbsoluteLength::Pixels(Pixels(8.)))),
 253            },
 254            background: Some(colors.editor_background.into()),
 255            text: Some(TextStyleRefinement {
 256                font_family: Some(theme_settings.buffer_font.family.clone()),
 257                font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
 258                font_features: Some(theme_settings.buffer_font.features.clone()),
 259                font_size: Some(buffer_font_size.into()),
 260                ..Default::default()
 261            }),
 262            ..Default::default()
 263        },
 264        inline_code: TextStyleRefinement {
 265            font_family: Some(theme_settings.buffer_font.family.clone()),
 266            font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
 267            font_features: Some(theme_settings.buffer_font.features.clone()),
 268            font_size: Some(buffer_font_size.into()),
 269            background_color: Some(colors.editor_foreground.opacity(0.08)),
 270            ..Default::default()
 271        },
 272        link: TextStyleRefinement {
 273            background_color: Some(colors.editor_foreground.opacity(0.025)),
 274            underline: Some(UnderlineStyle {
 275                color: Some(colors.text_accent.opacity(0.5)),
 276                thickness: px(1.),
 277                ..Default::default()
 278            }),
 279            ..Default::default()
 280        },
 281        link_callback: Some(Rc::new(move |url, cx| {
 282            if MentionLink::is_valid(url) {
 283                let colors = cx.theme().colors();
 284                Some(TextStyleRefinement {
 285                    background_color: Some(colors.element_background),
 286                    ..Default::default()
 287                })
 288            } else {
 289                None
 290            }
 291        })),
 292        ..Default::default()
 293    }
 294}
 295
 296fn tool_use_markdown_style(window: &Window, cx: &mut App) -> MarkdownStyle {
 297    let theme_settings = ThemeSettings::get_global(cx);
 298    let colors = cx.theme().colors();
 299    let ui_font_size = TextSize::Default.rems(cx);
 300    let buffer_font_size = TextSize::Small.rems(cx);
 301    let mut text_style = window.text_style();
 302
 303    text_style.refine(&TextStyleRefinement {
 304        font_family: Some(theme_settings.ui_font.family.clone()),
 305        font_fallbacks: theme_settings.ui_font.fallbacks.clone(),
 306        font_features: Some(theme_settings.ui_font.features.clone()),
 307        font_size: Some(ui_font_size.into()),
 308        color: Some(cx.theme().colors().text),
 309        ..Default::default()
 310    });
 311
 312    MarkdownStyle {
 313        base_text_style: text_style,
 314        syntax: cx.theme().syntax().clone(),
 315        selection_background_color: cx.theme().colors().element_selection_background,
 316        code_block_overflow_x_scroll: false,
 317        code_block: StyleRefinement {
 318            margin: EdgesRefinement::default(),
 319            padding: EdgesRefinement::default(),
 320            background: Some(colors.editor_background.into()),
 321            border_color: None,
 322            border_widths: EdgesRefinement::default(),
 323            text: Some(TextStyleRefinement {
 324                font_family: Some(theme_settings.buffer_font.family.clone()),
 325                font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
 326                font_features: Some(theme_settings.buffer_font.features.clone()),
 327                font_size: Some(buffer_font_size.into()),
 328                ..Default::default()
 329            }),
 330            ..Default::default()
 331        },
 332        inline_code: TextStyleRefinement {
 333            font_family: Some(theme_settings.buffer_font.family.clone()),
 334            font_fallbacks: theme_settings.buffer_font.fallbacks.clone(),
 335            font_features: Some(theme_settings.buffer_font.features.clone()),
 336            font_size: Some(TextSize::XSmall.rems(cx).into()),
 337            ..Default::default()
 338        },
 339        heading: StyleRefinement {
 340            text: Some(TextStyleRefinement {
 341                font_size: Some(ui_font_size.into()),
 342                ..Default::default()
 343            }),
 344            ..Default::default()
 345        },
 346        ..Default::default()
 347    }
 348}
 349
 350fn render_markdown_code_block(
 351    message_id: MessageId,
 352    ix: usize,
 353    kind: &CodeBlockKind,
 354    parsed_markdown: &ParsedMarkdown,
 355    metadata: CodeBlockMetadata,
 356    active_thread: Entity<ActiveThread>,
 357    workspace: WeakEntity<Workspace>,
 358    _window: &Window,
 359    cx: &App,
 360) -> Div {
 361    let label_size = rems(0.8125);
 362
 363    let label = match kind {
 364        CodeBlockKind::Indented => None,
 365        CodeBlockKind::Fenced => Some(
 366            h_flex()
 367                .px_1()
 368                .gap_1()
 369                .child(
 370                    Icon::new(IconName::Code)
 371                        .color(Color::Muted)
 372                        .size(IconSize::XSmall),
 373                )
 374                .child(div().text_size(label_size).child("Plain Text"))
 375                .into_any_element(),
 376        ),
 377        CodeBlockKind::FencedLang(raw_language_name) => Some(render_code_language(
 378            parsed_markdown.languages_by_name.get(raw_language_name),
 379            raw_language_name.clone(),
 380            cx,
 381        )),
 382        CodeBlockKind::FencedSrc(path_range) => path_range.path.file_name().map(|file_name| {
 383            // We tell the model to use /dev/null for the path instead of using ```language
 384            // because otherwise it consistently fails to use code citations.
 385            if path_range.path.starts_with("/dev/null") {
 386                let ext = path_range
 387                    .path
 388                    .extension()
 389                    .and_then(OsStr::to_str)
 390                    .map(|str| SharedString::new(str.to_string()))
 391                    .unwrap_or_default();
 392
 393                render_code_language(
 394                    parsed_markdown
 395                        .languages_by_path
 396                        .get(&path_range.path)
 397                        .or_else(|| parsed_markdown.languages_by_name.get(&ext)),
 398                    ext,
 399                    cx,
 400                )
 401            } else {
 402                let content = if let Some(parent) = path_range.path.parent() {
 403                    let file_name = file_name.to_string_lossy().to_string();
 404                    let path = parent.to_string_lossy().to_string();
 405                    let path_and_file = format!("{}/{}", path, file_name);
 406
 407                    h_flex()
 408                        .id(("code-block-header-label", ix))
 409                        .ml_1()
 410                        .gap_1()
 411                        .child(div().text_size(label_size).child(file_name))
 412                        .child(Label::new(path).color(Color::Muted).size(LabelSize::Small))
 413                        .tooltip(move |window, cx| {
 414                            Tooltip::with_meta(
 415                                "Jump to File",
 416                                None,
 417                                path_and_file.clone(),
 418                                window,
 419                                cx,
 420                            )
 421                        })
 422                        .into_any_element()
 423                } else {
 424                    div()
 425                        .ml_1()
 426                        .text_size(label_size)
 427                        .child(path_range.path.to_string_lossy().to_string())
 428                        .into_any_element()
 429                };
 430
 431                h_flex()
 432                    .id(("code-block-header-button", ix))
 433                    .w_full()
 434                    .max_w_full()
 435                    .px_1()
 436                    .gap_0p5()
 437                    .cursor_pointer()
 438                    .rounded_sm()
 439                    .hover(|item| item.bg(cx.theme().colors().element_hover.opacity(0.5)))
 440                    .child(
 441                        h_flex()
 442                            .gap_0p5()
 443                            .children(
 444                                file_icons::FileIcons::get_icon(&path_range.path, cx)
 445                                    .map(Icon::from_path)
 446                                    .map(|icon| icon.color(Color::Muted).size(IconSize::XSmall)),
 447                            )
 448                            .child(content)
 449                            .child(
 450                                Icon::new(IconName::ArrowUpRight)
 451                                    .size(IconSize::XSmall)
 452                                    .color(Color::Ignored),
 453                            ),
 454                    )
 455                    .on_click({
 456                        let path_range = path_range.clone();
 457                        move |_, window, cx| {
 458                            workspace
 459                                .update(cx, |workspace, cx| {
 460                                    open_path(&path_range, window, workspace, cx)
 461                                })
 462                                .ok();
 463                        }
 464                    })
 465                    .into_any_element()
 466            }
 467        }),
 468    };
 469
 470    let codeblock_was_copied = active_thread
 471        .read(cx)
 472        .copied_code_block_ids
 473        .contains(&(message_id, ix));
 474
 475    let is_expanded = active_thread.read(cx).is_codeblock_expanded(message_id, ix);
 476
 477    let codeblock_header_bg = cx
 478        .theme()
 479        .colors()
 480        .element_background
 481        .blend(cx.theme().colors().editor_foreground.opacity(0.025));
 482
 483    let control_buttons = h_flex()
 484        .visible_on_hover(CODEBLOCK_CONTAINER_GROUP)
 485        .absolute()
 486        .top_0()
 487        .right_0()
 488        .h_full()
 489        .bg(codeblock_header_bg)
 490        .rounded_tr_md()
 491        .px_1()
 492        .gap_1()
 493        .child(
 494            IconButton::new(
 495                ("copy-markdown-code", ix),
 496                if codeblock_was_copied {
 497                    IconName::Check
 498                } else {
 499                    IconName::Copy
 500                },
 501            )
 502            .icon_color(Color::Muted)
 503            .shape(ui::IconButtonShape::Square)
 504            .tooltip(Tooltip::text("Copy Code"))
 505            .on_click({
 506                let active_thread = active_thread.clone();
 507                let parsed_markdown = parsed_markdown.clone();
 508                let code_block_range = metadata.content_range.clone();
 509                move |_event, _window, cx| {
 510                    active_thread.update(cx, |this, cx| {
 511                        this.copied_code_block_ids.insert((message_id, ix));
 512
 513                        let code = parsed_markdown.source()[code_block_range.clone()].to_string();
 514                        cx.write_to_clipboard(ClipboardItem::new_string(code));
 515
 516                        cx.spawn(async move |this, cx| {
 517                            cx.background_executor().timer(Duration::from_secs(2)).await;
 518
 519                            cx.update(|cx| {
 520                                this.update(cx, |this, cx| {
 521                                    this.copied_code_block_ids.remove(&(message_id, ix));
 522                                    cx.notify();
 523                                })
 524                            })
 525                            .ok();
 526                        })
 527                        .detach();
 528                    });
 529                }
 530            }),
 531        )
 532        .child(
 533            IconButton::new(
 534                ("expand-collapse-code", ix),
 535                if is_expanded {
 536                    IconName::ChevronUp
 537                } else {
 538                    IconName::ChevronDown
 539                },
 540            )
 541            .icon_color(Color::Muted)
 542            .shape(ui::IconButtonShape::Square)
 543            .tooltip(Tooltip::text(if is_expanded {
 544                "Collapse Code"
 545            } else {
 546                "Expand Code"
 547            }))
 548            .on_click({
 549                let active_thread = active_thread.clone();
 550                move |_event, _window, cx| {
 551                    active_thread.update(cx, |this, cx| {
 552                        this.toggle_codeblock_expanded(message_id, ix);
 553                        cx.notify();
 554                    });
 555                }
 556            }),
 557        );
 558
 559    let codeblock_header = h_flex()
 560        .relative()
 561        .p_1()
 562        .gap_1()
 563        .justify_between()
 564        .bg(codeblock_header_bg)
 565        .map(|this| {
 566            if !is_expanded {
 567                this.rounded_md()
 568            } else {
 569                this.rounded_t_md()
 570                    .border_b_1()
 571                    .border_color(cx.theme().colors().border.opacity(0.6))
 572            }
 573        })
 574        .children(label)
 575        .child(control_buttons);
 576
 577    v_flex()
 578        .group(CODEBLOCK_CONTAINER_GROUP)
 579        .my_2()
 580        .overflow_hidden()
 581        .rounded_md()
 582        .border_1()
 583        .border_color(cx.theme().colors().border.opacity(0.6))
 584        .bg(cx.theme().colors().editor_background)
 585        .child(codeblock_header)
 586        .when(!is_expanded, |this| this.h(rems_from_px(31.)))
 587}
 588
 589fn open_path(
 590    path_range: &PathWithRange,
 591    window: &mut Window,
 592    workspace: &mut Workspace,
 593    cx: &mut Context<'_, Workspace>,
 594) {
 595    let Some(project_path) = workspace
 596        .project()
 597        .read(cx)
 598        .find_project_path(&path_range.path, cx)
 599    else {
 600        return; // TODO instead of just bailing out, open that path in a buffer.
 601    };
 602
 603    let Some(target) = path_range.range.as_ref().map(|range| {
 604        Point::new(
 605            // Line number is 1-based
 606            range.start.line.saturating_sub(1),
 607            range.start.col.unwrap_or(0),
 608        )
 609    }) else {
 610        return;
 611    };
 612    let open_task = workspace.open_path(project_path, None, true, window, cx);
 613    window
 614        .spawn(cx, async move |cx| {
 615            let item = open_task.await?;
 616            if let Some(active_editor) = item.downcast::<Editor>() {
 617                active_editor
 618                    .update_in(cx, |editor, window, cx| {
 619                        editor.go_to_singleton_buffer_point(target, window, cx);
 620                    })
 621                    .ok();
 622            }
 623            anyhow::Ok(())
 624        })
 625        .detach_and_log_err(cx);
 626}
 627
 628fn render_code_language(
 629    language: Option<&Arc<Language>>,
 630    name_fallback: SharedString,
 631    cx: &App,
 632) -> AnyElement {
 633    let icon_path = language.and_then(|language| {
 634        language
 635            .config()
 636            .matcher
 637            .path_suffixes
 638            .iter()
 639            .find_map(|extension| file_icons::FileIcons::get_icon(Path::new(extension), cx))
 640            .map(Icon::from_path)
 641    });
 642
 643    let language_label = language
 644        .map(|language| language.name().into())
 645        .unwrap_or(name_fallback);
 646
 647    let label_size = rems(0.8125);
 648
 649    h_flex()
 650        .px_1()
 651        .gap_1p5()
 652        .children(icon_path.map(|icon| icon.color(Color::Muted).size(IconSize::XSmall)))
 653        .child(div().text_size(label_size).child(language_label))
 654        .into_any_element()
 655}
 656
 657fn open_markdown_link(
 658    text: SharedString,
 659    workspace: WeakEntity<Workspace>,
 660    window: &mut Window,
 661    cx: &mut App,
 662) {
 663    let Some(workspace) = workspace.upgrade() else {
 664        cx.open_url(&text);
 665        return;
 666    };
 667
 668    match MentionLink::try_parse(&text, &workspace, cx) {
 669        Some(MentionLink::File(path, entry)) => workspace.update(cx, |workspace, cx| {
 670            if entry.is_dir() {
 671                workspace.project().update(cx, |_, cx| {
 672                    cx.emit(project::Event::RevealInProjectPanel(entry.id));
 673                })
 674            } else {
 675                workspace
 676                    .open_path(path, None, true, window, cx)
 677                    .detach_and_log_err(cx);
 678            }
 679        }),
 680        Some(MentionLink::Symbol(path, symbol_name)) => {
 681            let open_task = workspace.update(cx, |workspace, cx| {
 682                workspace.open_path(path, None, true, window, cx)
 683            });
 684            window
 685                .spawn(cx, async move |cx| {
 686                    let active_editor = open_task
 687                        .await?
 688                        .downcast::<Editor>()
 689                        .context("Item is not an editor")?;
 690                    active_editor.update_in(cx, |editor, window, cx| {
 691                        let symbol_range = editor
 692                            .buffer()
 693                            .read(cx)
 694                            .snapshot(cx)
 695                            .outline(None)
 696                            .and_then(|outline| {
 697                                outline
 698                                    .find_most_similar(&symbol_name)
 699                                    .map(|(_, item)| item.range.clone())
 700                            })
 701                            .context("Could not find matching symbol")?;
 702
 703                        editor.change_selections(
 704                            SelectionEffects::scroll(Autoscroll::center()),
 705                            window,
 706                            cx,
 707                            |s| s.select_anchor_ranges([symbol_range.start..symbol_range.start]),
 708                        );
 709                        anyhow::Ok(())
 710                    })
 711                })
 712                .detach_and_log_err(cx);
 713        }
 714        Some(MentionLink::Selection(path, line_range)) => {
 715            let open_task = workspace.update(cx, |workspace, cx| {
 716                workspace.open_path(path, None, true, window, cx)
 717            });
 718            window
 719                .spawn(cx, async move |cx| {
 720                    let active_editor = open_task
 721                        .await?
 722                        .downcast::<Editor>()
 723                        .context("Item is not an editor")?;
 724                    active_editor.update_in(cx, |editor, window, cx| {
 725                        editor.change_selections(
 726                            SelectionEffects::scroll(Autoscroll::center()),
 727                            window,
 728                            cx,
 729                            |s| {
 730                                s.select_ranges([Point::new(line_range.start as u32, 0)
 731                                    ..Point::new(line_range.start as u32, 0)])
 732                            },
 733                        );
 734                        anyhow::Ok(())
 735                    })
 736                })
 737                .detach_and_log_err(cx);
 738        }
 739        Some(MentionLink::Thread(thread_id)) => workspace.update(cx, |workspace, cx| {
 740            if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 741                panel.update(cx, |panel, cx| {
 742                    panel
 743                        .open_thread_by_id(&thread_id, window, cx)
 744                        .detach_and_log_err(cx)
 745                });
 746            }
 747        }),
 748        Some(MentionLink::TextThread(path)) => workspace.update(cx, |workspace, cx| {
 749            if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
 750                panel.update(cx, |panel, cx| {
 751                    panel
 752                        .open_saved_prompt_editor(path, window, cx)
 753                        .detach_and_log_err(cx);
 754                });
 755            }
 756        }),
 757        Some(MentionLink::Fetch(url)) => cx.open_url(&url),
 758        Some(MentionLink::Rule(prompt_id)) => window.dispatch_action(
 759            Box::new(OpenRulesLibrary {
 760                prompt_to_select: Some(prompt_id.0),
 761            }),
 762            cx,
 763        ),
 764        None => cx.open_url(&text),
 765    }
 766}
 767
 768struct EditingMessageState {
 769    editor: Entity<Editor>,
 770    context_strip: Entity<ContextStrip>,
 771    context_picker_menu_handle: PopoverMenuHandle<ContextPicker>,
 772    last_estimated_token_count: Option<u64>,
 773    _subscriptions: [Subscription; 2],
 774    _update_token_count_task: Option<Task<()>>,
 775    is_agent_message: bool,
 776    preprocessing_applied: bool,
 777}
 778
 779impl ActiveThread {
 780    pub fn new(
 781        thread: Entity<Thread>,
 782        thread_store: Entity<ThreadStore>,
 783        text_thread_store: Entity<TextThreadStore>,
 784        context_store: Entity<ContextStore>,
 785        language_registry: Arc<LanguageRegistry>,
 786        workspace: WeakEntity<Workspace>,
 787        window: &mut Window,
 788        cx: &mut Context<Self>,
 789    ) -> Self {
 790        let subscriptions = vec![
 791            cx.observe(&thread, |_, _, cx| cx.notify()),
 792            cx.subscribe_in(&thread, window, Self::handle_thread_event),
 793            cx.subscribe(&thread_store, Self::handle_rules_loading_error),
 794            cx.observe_global::<SettingsStore>(|_, cx| cx.notify()),
 795        ];
 796
 797        let list_state = ListState::new(0, ListAlignment::Bottom, px(2048.), {
 798            let this = cx.entity().downgrade();
 799            move |ix, window: &mut Window, cx: &mut App| {
 800                this.update(cx, |this, cx| this.render_message(ix, window, cx))
 801                    .unwrap()
 802            }
 803        });
 804
 805        let workspace_subscription = if let Some(workspace) = workspace.upgrade() {
 806            Some(cx.observe_release(&workspace, |this, _, cx| {
 807                this.dismiss_notifications(cx);
 808            }))
 809        } else {
 810            None
 811        };
 812
 813        let mut this = Self {
 814            language_registry,
 815            thread_store,
 816            text_thread_store,
 817            context_store,
 818            thread: thread.clone(),
 819            workspace,
 820            save_thread_task: None,
 821            messages: Vec::new(),
 822            rendered_messages_by_id: HashMap::default(),
 823            rendered_tool_uses: HashMap::default(),
 824            expanded_tool_uses: HashMap::default(),
 825            expanded_thinking_segments: HashMap::default(),
 826            expanded_code_blocks: HashMap::default(),
 827            list_state: list_state.clone(),
 828            scrollbar_state: ScrollbarState::new(list_state),
 829            show_scrollbar: false,
 830            hide_scrollbar_task: None,
 831            editing_message: None,
 832            last_error: None,
 833            copied_code_block_ids: HashSet::default(),
 834            notifications: Vec::new(),
 835            _subscriptions: subscriptions,
 836            notification_subscriptions: HashMap::default(),
 837            open_feedback_editors: HashMap::default(),
 838            _load_edited_message_context_task: None,
 839        };
 840
 841        for message in thread.read(cx).messages().cloned().collect::<Vec<_>>() {
 842            let rendered_message = RenderedMessage::from_segments(
 843                &message.segments,
 844                this.language_registry.clone(),
 845                cx,
 846            );
 847            this.push_rendered_message(message.id, rendered_message);
 848
 849            for tool_use in thread.read(cx).tool_uses_for_message(message.id, cx) {
 850                this.render_tool_use_markdown(
 851                    tool_use.id.clone(),
 852                    tool_use.ui_text.clone(),
 853                    &serde_json::to_string_pretty(&tool_use.input).unwrap_or_default(),
 854                    tool_use.status.text(),
 855                    cx,
 856                );
 857            }
 858        }
 859
 860        if let Some(subscription) = workspace_subscription {
 861            this._subscriptions.push(subscription);
 862        }
 863
 864        this
 865    }
 866
 867    pub fn thread(&self) -> &Entity<Thread> {
 868        &self.thread
 869    }
 870
 871    pub fn is_empty(&self) -> bool {
 872        self.messages.is_empty()
 873    }
 874
 875    pub fn summary<'a>(&'a self, cx: &'a App) -> &'a ThreadSummary {
 876        self.thread.read(cx).summary()
 877    }
 878
 879    pub fn regenerate_summary(&self, cx: &mut App) {
 880        self.thread.update(cx, |thread, cx| thread.summarize(cx))
 881    }
 882
 883    pub fn cancel_last_completion(&mut self, window: &mut Window, cx: &mut App) -> bool {
 884        self.last_error.take();
 885        self.thread.update(cx, |thread, cx| {
 886            thread.cancel_last_completion(Some(window.window_handle()), cx)
 887        })
 888    }
 889
 890    pub fn last_error(&self) -> Option<ThreadError> {
 891        self.last_error.clone()
 892    }
 893
 894    pub fn clear_last_error(&mut self) {
 895        self.last_error.take();
 896    }
 897
 898    /// Returns the editing message id and the estimated token count in the content
 899    pub fn editing_message_id(&self) -> Option<(MessageId, u64)> {
 900        self.editing_message
 901            .as_ref()
 902            .map(|(id, state)| (*id, state.last_estimated_token_count.unwrap_or(0)))
 903    }
 904
 905    pub fn context_store(&self) -> &Entity<ContextStore> {
 906        &self.context_store
 907    }
 908
 909    pub fn thread_store(&self) -> &Entity<ThreadStore> {
 910        &self.thread_store
 911    }
 912
 913    pub fn text_thread_store(&self) -> &Entity<TextThreadStore> {
 914        &self.text_thread_store
 915    }
 916
 917    fn push_rendered_message(&mut self, id: MessageId, rendered_message: RenderedMessage) {
 918        let old_len = self.messages.len();
 919        self.messages.push(id);
 920        self.list_state.splice(old_len..old_len, 1);
 921        self.rendered_messages_by_id.insert(id, rendered_message);
 922    }
 923
 924    fn deleted_message(&mut self, id: &MessageId) {
 925        let Some(index) = self.messages.iter().position(|message_id| message_id == id) else {
 926            return;
 927        };
 928        self.messages.remove(index);
 929        self.list_state.splice(index..index + 1, 0);
 930        self.rendered_messages_by_id.remove(id);
 931    }
 932
 933    pub fn edit_last_message(&mut self, role: Role, window: &mut Window, cx: &mut Context<Self>) {
 934        if self.editing_message_id().is_some() {
 935            return;
 936        }
 937
 938        // smit
 939        let thread = self.thread().read(cx);
 940        let messages: Vec<_> = thread.messages().collect();
 941
 942        let Some(message) = messages.iter().rev().find(|m| m.role == role) else {
 943            return;
 944        };
 945
 946        let (id, segments, creases) = (
 947            message.id,
 948            message.segments.clone(),
 949            message.creases.clone(),
 950        );
 951
 952        match role {
 953            Role::Assistant => {
 954                self.start_editing_assistant_message(id, &segments, &creases, None, window, cx)
 955            }
 956            Role::User => {
 957                dbg!(&segments);
 958
 959                if let Some(message_text) = segments.first().and_then(|segment| match segment {
 960                    MessageSegment::Text(message_text) => {
 961                        Some(Into::<Arc<str>>::into(message_text.as_str()))
 962                    }
 963                    _ => None,
 964                }) {
 965                    dbg!(&message_text);
 966
 967                    self.start_editing_user_message(id, message_text, &creases, None, window, cx)
 968                }
 969            }
 970            _ => {}
 971        }
 972    }
 973
 974    fn render_tool_use_markdown(
 975        &mut self,
 976        tool_use_id: LanguageModelToolUseId,
 977        tool_label: impl Into<SharedString>,
 978        tool_input: &str,
 979        tool_output: SharedString,
 980        cx: &mut Context<Self>,
 981    ) {
 982        let rendered = self
 983            .rendered_tool_uses
 984            .entry(tool_use_id.clone())
 985            .or_insert_with(|| RenderedToolUse {
 986                label: cx.new(|cx| {
 987                    Markdown::new("".into(), Some(self.language_registry.clone()), None, cx)
 988                }),
 989                input: cx.new(|cx| {
 990                    Markdown::new("".into(), Some(self.language_registry.clone()), None, cx)
 991                }),
 992                output: cx.new(|cx| {
 993                    Markdown::new("".into(), Some(self.language_registry.clone()), None, cx)
 994                }),
 995            });
 996
 997        rendered.label.update(cx, |this, cx| {
 998            this.replace(tool_label, cx);
 999        });
1000        rendered.input.update(cx, |this, cx| {
1001            this.replace(
1002                MarkdownCodeBlock {
1003                    tag: "json",
1004                    text: tool_input,
1005                }
1006                .to_string(),
1007                cx,
1008            );
1009        });
1010        rendered.output.update(cx, |this, cx| {
1011            this.replace(tool_output, cx);
1012        });
1013    }
1014
1015    fn handle_thread_event(
1016        &mut self,
1017        _thread: &Entity<Thread>,
1018        event: &ThreadEvent,
1019        window: &mut Window,
1020        cx: &mut Context<Self>,
1021    ) {
1022        match event {
1023            ThreadEvent::CancelEditing => {
1024                if self.editing_message.is_some() {
1025                    self.cancel_editing_message(&menu::Cancel, window, cx);
1026                }
1027            }
1028            ThreadEvent::ShowError(error) => {
1029                self.last_error = Some(error.clone());
1030            }
1031            ThreadEvent::NewRequest => {
1032                cx.notify();
1033            }
1034            ThreadEvent::CompletionCanceled => {
1035                self.thread.update(cx, |thread, cx| {
1036                    thread.project().update(cx, |project, cx| {
1037                        project.set_agent_location(None, cx);
1038                    })
1039                });
1040                self.workspace
1041                    .update(cx, |workspace, cx| {
1042                        if workspace.is_being_followed(CollaboratorId::Agent) {
1043                            workspace.unfollow(CollaboratorId::Agent, window, cx);
1044                        }
1045                    })
1046                    .ok();
1047                cx.notify();
1048            }
1049            ThreadEvent::StreamedCompletion
1050            | ThreadEvent::SummaryGenerated
1051            | ThreadEvent::SummaryChanged => {
1052                self.save_thread(cx);
1053            }
1054            ThreadEvent::Stopped(reason) => {
1055                match reason {
1056                    Ok(StopReason::EndTurn | StopReason::MaxTokens) => {
1057                        let used_tools = self.thread.read(cx).used_tools_since_last_user_message();
1058                        self.notify_with_sound(
1059                            if used_tools {
1060                                "Finished running tools"
1061                            } else {
1062                                "New message"
1063                            },
1064                            IconName::ZedAssistant,
1065                            window,
1066                            cx,
1067                        );
1068                    }
1069                    Ok(StopReason::ToolUse) => {
1070                        // Don't notify for intermediate tool use
1071                    }
1072                    Ok(StopReason::Refusal) => {
1073                        self.notify_with_sound(
1074                            "Language model refused to respond",
1075                            IconName::Warning,
1076                            window,
1077                            cx,
1078                        );
1079                    }
1080                    Err(error) => {
1081                        self.notify_with_sound(
1082                            "Agent stopped due to an error",
1083                            IconName::Warning,
1084                            window,
1085                            cx,
1086                        );
1087
1088                        let error_message = error
1089                            .chain()
1090                            .map(|err| err.to_string())
1091                            .collect::<Vec<_>>()
1092                            .join("\n");
1093                        self.last_error = Some(ThreadError::Message {
1094                            header: "Error".into(),
1095                            message: error_message.into(),
1096                        });
1097                    }
1098                }
1099            }
1100            ThreadEvent::ToolConfirmationNeeded => {
1101                self.notify_with_sound("Waiting for tool confirmation", IconName::Info, window, cx);
1102            }
1103            ThreadEvent::ToolUseLimitReached => {
1104                self.notify_with_sound(
1105                    "Consecutive tool use limit reached.",
1106                    IconName::Warning,
1107                    window,
1108                    cx,
1109                );
1110            }
1111            ThreadEvent::StreamedAssistantText(message_id, text) => {
1112                if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
1113                    rendered_message.append_text(text, cx);
1114                }
1115            }
1116            ThreadEvent::StreamedAssistantThinking(message_id, text) => {
1117                if let Some(rendered_message) = self.rendered_messages_by_id.get_mut(&message_id) {
1118                    rendered_message.append_thinking(text, cx);
1119                }
1120            }
1121            ThreadEvent::MessageAdded(message_id) => {
1122                self.clear_last_error();
1123                if let Some(rendered_message) = self.thread.update(cx, |thread, cx| {
1124                    thread.message(*message_id).map(|message| {
1125                        RenderedMessage::from_segments(
1126                            &message.segments,
1127                            self.language_registry.clone(),
1128                            cx,
1129                        )
1130                    })
1131                }) {
1132                    self.push_rendered_message(*message_id, rendered_message);
1133                }
1134
1135                self.save_thread(cx);
1136                cx.notify();
1137            }
1138            ThreadEvent::MessageEdited(message_id) => {
1139                self.clear_last_error();
1140                if let Some(index) = self.messages.iter().position(|id| id == message_id) {
1141                    if let Some(rendered_message) = self.thread.update(cx, |thread, cx| {
1142                        thread.message(*message_id).map(|message| {
1143                            let mut rendered_message = RenderedMessage {
1144                                language_registry: self.language_registry.clone(),
1145                                segments: Vec::with_capacity(message.segments.len()),
1146                            };
1147                            for segment in &message.segments {
1148                                rendered_message.push_segment(segment, cx);
1149                            }
1150                            rendered_message
1151                        })
1152                    }) {
1153                        self.list_state.splice(index..index + 1, 1);
1154                        self.rendered_messages_by_id
1155                            .insert(*message_id, rendered_message);
1156                        self.scroll_to_bottom(cx);
1157                        self.save_thread(cx);
1158                        cx.notify();
1159                    }
1160                }
1161            }
1162            ThreadEvent::MessageDeleted(message_id) => {
1163                self.deleted_message(message_id);
1164                self.save_thread(cx);
1165                cx.notify();
1166            }
1167            ThreadEvent::UsePendingTools { tool_uses } => {
1168                for tool_use in tool_uses {
1169                    self.render_tool_use_markdown(
1170                        tool_use.id.clone(),
1171                        tool_use.ui_text.clone(),
1172                        &serde_json::to_string_pretty(&tool_use.input).unwrap_or_default(),
1173                        "".into(),
1174                        cx,
1175                    );
1176                }
1177            }
1178            ThreadEvent::StreamedToolUse {
1179                tool_use_id,
1180                ui_text,
1181                input,
1182            } => {
1183                self.render_tool_use_markdown(
1184                    tool_use_id.clone(),
1185                    ui_text.clone(),
1186                    &serde_json::to_string_pretty(&input).unwrap_or_default(),
1187                    "".into(),
1188                    cx,
1189                );
1190            }
1191            ThreadEvent::ToolFinished {
1192                pending_tool_use, ..
1193            } => {
1194                if let Some(tool_use) = pending_tool_use {
1195                    self.render_tool_use_markdown(
1196                        tool_use.id.clone(),
1197                        tool_use.ui_text.clone(),
1198                        &serde_json::to_string_pretty(&tool_use.input).unwrap_or_default(),
1199                        self.thread
1200                            .read(cx)
1201                            .output_for_tool(&tool_use.id)
1202                            .map(|output| output.clone().into())
1203                            .unwrap_or("".into()),
1204                        cx,
1205                    );
1206                }
1207            }
1208            ThreadEvent::CheckpointChanged => cx.notify(),
1209            ThreadEvent::ReceivedTextChunk => {}
1210            ThreadEvent::InvalidToolInput {
1211                tool_use_id,
1212                ui_text,
1213                invalid_input_json,
1214            } => {
1215                self.render_tool_use_markdown(
1216                    tool_use_id.clone(),
1217                    ui_text,
1218                    invalid_input_json,
1219                    self.thread
1220                        .read(cx)
1221                        .output_for_tool(tool_use_id)
1222                        .map(|output| output.clone().into())
1223                        .unwrap_or("".into()),
1224                    cx,
1225                );
1226            }
1227            ThreadEvent::MissingToolUse {
1228                tool_use_id,
1229                ui_text,
1230            } => {
1231                self.render_tool_use_markdown(
1232                    tool_use_id.clone(),
1233                    ui_text,
1234                    "",
1235                    self.thread
1236                        .read(cx)
1237                        .output_for_tool(tool_use_id)
1238                        .map(|output| output.clone().into())
1239                        .unwrap_or("".into()),
1240                    cx,
1241                );
1242            }
1243            ThreadEvent::ProfileChanged => {
1244                self.save_thread(cx);
1245                cx.notify();
1246            }
1247        }
1248    }
1249
1250    fn handle_rules_loading_error(
1251        &mut self,
1252        _thread_store: Entity<ThreadStore>,
1253        error: &RulesLoadingError,
1254        cx: &mut Context<Self>,
1255    ) {
1256        self.last_error = Some(ThreadError::Message {
1257            header: "Error loading rules file".into(),
1258            message: error.message.clone(),
1259        });
1260        cx.notify();
1261    }
1262
1263    fn play_notification_sound(&self, window: &Window, cx: &mut App) {
1264        let settings = AgentSettings::get_global(cx);
1265        if settings.play_sound_when_agent_done && !window.is_window_active() {
1266            Audio::play_sound(Sound::AgentDone, cx);
1267        }
1268    }
1269
1270    fn show_notification(
1271        &mut self,
1272        caption: impl Into<SharedString>,
1273        icon: IconName,
1274        window: &mut Window,
1275        cx: &mut Context<ActiveThread>,
1276    ) {
1277        if window.is_window_active() || !self.notifications.is_empty() {
1278            return;
1279        }
1280
1281        let title = self.thread.read(cx).summary().unwrap_or("Agent Panel");
1282
1283        match AgentSettings::get_global(cx).notify_when_agent_waiting {
1284            NotifyWhenAgentWaiting::PrimaryScreen => {
1285                if let Some(primary) = cx.primary_display() {
1286                    self.pop_up(icon, caption.into(), title.clone(), window, primary, cx);
1287                }
1288            }
1289            NotifyWhenAgentWaiting::AllScreens => {
1290                let caption = caption.into();
1291                for screen in cx.displays() {
1292                    self.pop_up(icon, caption.clone(), title.clone(), window, screen, cx);
1293                }
1294            }
1295            NotifyWhenAgentWaiting::Never => {
1296                // Don't show anything
1297            }
1298        }
1299    }
1300
1301    fn notify_with_sound(
1302        &mut self,
1303        caption: impl Into<SharedString>,
1304        icon: IconName,
1305        window: &mut Window,
1306        cx: &mut Context<ActiveThread>,
1307    ) {
1308        self.play_notification_sound(window, cx);
1309        self.show_notification(caption, icon, window, cx);
1310    }
1311
1312    fn pop_up(
1313        &mut self,
1314        icon: IconName,
1315        caption: SharedString,
1316        title: SharedString,
1317        window: &mut Window,
1318        screen: Rc<dyn PlatformDisplay>,
1319        cx: &mut Context<'_, ActiveThread>,
1320    ) {
1321        let options = AgentNotification::window_options(screen, cx);
1322
1323        let project_name = self.workspace.upgrade().and_then(|workspace| {
1324            workspace
1325                .read(cx)
1326                .project()
1327                .read(cx)
1328                .visible_worktrees(cx)
1329                .next()
1330                .map(|worktree| worktree.read(cx).root_name().to_string())
1331        });
1332
1333        if let Some(screen_window) = cx
1334            .open_window(options, |_, cx| {
1335                cx.new(|_| {
1336                    AgentNotification::new(title.clone(), caption.clone(), icon, project_name)
1337                })
1338            })
1339            .log_err()
1340        {
1341            if let Some(pop_up) = screen_window.entity(cx).log_err() {
1342                self.notification_subscriptions
1343                    .entry(screen_window)
1344                    .or_insert_with(Vec::new)
1345                    .push(cx.subscribe_in(&pop_up, window, {
1346                        |this, _, event, window, cx| match event {
1347                            AgentNotificationEvent::Accepted => {
1348                                let handle = window.window_handle();
1349                                cx.activate(true);
1350
1351                                let workspace_handle = this.workspace.clone();
1352
1353                                // If there are multiple Zed windows, activate the correct one.
1354                                cx.defer(move |cx| {
1355                                    handle
1356                                        .update(cx, |_view, window, _cx| {
1357                                            window.activate_window();
1358
1359                                            if let Some(workspace) = workspace_handle.upgrade() {
1360                                                workspace.update(_cx, |workspace, cx| {
1361                                                    workspace.focus_panel::<AgentPanel>(window, cx);
1362                                                });
1363                                            }
1364                                        })
1365                                        .log_err();
1366                                });
1367
1368                                this.dismiss_notifications(cx);
1369                            }
1370                            AgentNotificationEvent::Dismissed => {
1371                                this.dismiss_notifications(cx);
1372                            }
1373                        }
1374                    }));
1375
1376                self.notifications.push(screen_window);
1377
1378                // If the user manually refocuses the original window, dismiss the popup.
1379                self.notification_subscriptions
1380                    .entry(screen_window)
1381                    .or_insert_with(Vec::new)
1382                    .push({
1383                        let pop_up_weak = pop_up.downgrade();
1384
1385                        cx.observe_window_activation(window, move |_, window, cx| {
1386                            if window.is_window_active() {
1387                                if let Some(pop_up) = pop_up_weak.upgrade() {
1388                                    pop_up.update(cx, |_, cx| {
1389                                        cx.emit(AgentNotificationEvent::Dismissed);
1390                                    });
1391                                }
1392                            }
1393                        })
1394                    });
1395            }
1396        }
1397    }
1398
1399    /// Spawns a task to save the active thread.
1400    ///
1401    /// Only one task to save the thread will be in flight at a time.
1402    fn save_thread(&mut self, cx: &mut Context<Self>) {
1403        let thread = self.thread.clone();
1404        self.save_thread_task = Some(cx.spawn(async move |this, cx| {
1405            let task = this
1406                .update(cx, |this, cx| {
1407                    this.thread_store
1408                        .update(cx, |thread_store, cx| thread_store.save_thread(&thread, cx))
1409                })
1410                .ok();
1411
1412            if let Some(task) = task {
1413                task.await.log_err();
1414            }
1415        }));
1416    }
1417
1418    fn start_editing_user_message(
1419        &mut self,
1420        message_id: MessageId,
1421        message_text: impl Into<Arc<str>>,
1422        message_creases: &[MessageCrease],
1423        text_offset: Option<usize>,
1424        window: &mut Window,
1425        cx: &mut Context<Self>,
1426    ) {
1427        let editor = crate::message_editor::create_editor(
1428            self.workspace.clone(),
1429            self.context_store.downgrade(),
1430            self.thread_store.downgrade(),
1431            self.text_thread_store.downgrade(),
1432            EDIT_PREVIOUS_MESSAGE_MIN_LINES,
1433            None,
1434            window,
1435            cx,
1436        );
1437
1438        editor.update(cx, |editor, cx| {
1439            editor.set_text(message_text, window, cx);
1440            insert_message_creases(editor, message_creases, &self.context_store, window, cx);
1441            editor.focus_handle(cx).focus(window);
1442
1443            if let Some(offset) = text_offset {
1444                let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
1445                let text = buffer_snapshot.text();
1446                let clamped_offset = offset.min(text.len());
1447                let point = buffer_snapshot.offset_to_point(clamped_offset);
1448                let anchor = buffer_snapshot.anchor_before(point);
1449                editor.change_selections(
1450                    SelectionEffects::scroll(Autoscroll::center()),
1451                    window,
1452                    cx,
1453                    |s| s.select_ranges([anchor..anchor]),
1454                );
1455            } else {
1456                editor.move_to_end(&MoveToEnd, window, cx);
1457            }
1458        });
1459
1460        let buffer_edited_subscription = cx.subscribe(&editor, |this, _, event, cx| {
1461            if matches!(event, EditorEvent::BufferEdited) {
1462                this.update_editing_message_token_count(true, cx);
1463            }
1464        });
1465
1466        let context_picker_menu_handle = PopoverMenuHandle::default();
1467        let context_strip = cx.new(|cx| {
1468            ContextStrip::new(
1469                self.context_store.clone(),
1470                self.workspace.clone(),
1471                Some(self.thread_store.downgrade()),
1472                Some(self.text_thread_store.downgrade()),
1473                context_picker_menu_handle.clone(),
1474                SuggestContextKind::File,
1475                ModelUsageContext::Thread(self.thread.clone()),
1476                window,
1477                cx,
1478            )
1479        });
1480
1481        let context_strip_subscription =
1482            cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event);
1483
1484        self.editing_message = Some((
1485            message_id,
1486            EditingMessageState {
1487                editor: editor.clone(),
1488                context_strip,
1489                context_picker_menu_handle,
1490                last_estimated_token_count: None,
1491                _subscriptions: [buffer_edited_subscription, context_strip_subscription],
1492                _update_token_count_task: None,
1493                is_agent_message: false,
1494                preprocessing_applied: false,
1495            },
1496        ));
1497        self.update_editing_message_token_count(false, cx);
1498
1499        if let Some(message_index) = self.messages.iter().position(|id| *id == message_id) {
1500            self.list_state.scroll_to_reveal_item(message_index);
1501        }
1502
1503        cx.notify();
1504    }
1505
1506    fn start_editing_assistant_message_at_segment(
1507        &mut self,
1508        message_id: MessageId,
1509        segment_index: usize,
1510        segment_offset: usize,
1511        window: &mut Window,
1512        cx: &mut Context<Self>,
1513    ) {
1514        let thread = self.thread.read(cx);
1515        let Some(message) = thread.message(message_id) else {
1516            return;
1517        };
1518        let message_segments = message.segments.clone();
1519        let message_creases = message.creases.clone();
1520
1521        let total_offset = segment_offset
1522            + message_segments
1523                .iter()
1524                .filter(|s| !matches!(s, MessageSegment::RedactedThinking(_)))
1525                .take(segment_index)
1526                .map(|s| match s {
1527                    MessageSegment::Text(text) | MessageSegment::Thinking { text, .. } => {
1528                        text.len() + 2 // \n\n
1529                    }
1530                    _ => 0,
1531                })
1532                .sum::<usize>();
1533
1534        self.start_editing_assistant_message(
1535            message_id,
1536            &message_segments,
1537            &message_creases,
1538            Some(total_offset),
1539            window,
1540            cx,
1541        );
1542    }
1543
1544    pub fn start_editing_assistant_message(
1545        &mut self,
1546        message_id: MessageId,
1547        message_segments: &[MessageSegment],
1548        message_creases: &[MessageCrease],
1549        text_offset: Option<usize>,
1550        window: &mut Window,
1551        cx: &mut Context<Self>,
1552    ) {
1553        let message_text = message_segments
1554            .iter()
1555            .filter_map(|segment| match segment {
1556                MessageSegment::Text(text) => Some(text.clone()),
1557                MessageSegment::Thinking { text, .. } => {
1558                    Some(format!("<think>\n{}\n</think>", text))
1559                }
1560                MessageSegment::RedactedThinking(_) => None,
1561            })
1562            .collect::<Vec<_>>()
1563            .join("\n\n");
1564
1565        let preprocessed_text = PATH_CODE_BLOCK_REGEX
1566            .replace_all(&message_text, "```$2 $1")
1567            .to_string();
1568        let preprocessing_applied = preprocessed_text != message_text;
1569
1570        let editor = cx.new(|cx| {
1571            let buffer =
1572                cx.new(|cx| MultiBuffer::singleton(cx.new(|cx| Buffer::local("", cx)), cx));
1573            let mut e = Editor::new(
1574                editor::EditorMode::AutoHeight {
1575                    min_lines: 1,
1576                    max_lines: Some(ASSISTANT_EDITOR_MAX_LINES),
1577                },
1578                buffer,
1579                None,
1580                window,
1581                cx,
1582            );
1583            e.set_show_line_numbers(EditorSettings::get_global(cx).gutter.line_numbers, cx);
1584            e.set_show_indent_guides(true, cx);
1585            e.set_soft_wrap();
1586            e.set_use_modal_editing(true);
1587            e
1588        });
1589
1590        editor.update(cx, |e, cx| {
1591            e.set_text(message_text, window, cx);
1592            insert_message_creases(e, message_creases, &self.context_store, window, cx);
1593            e.focus_handle(cx).focus(window);
1594        });
1595
1596        if let Some(offset) = text_offset {
1597            let point = editor.update(cx, |editor, cx| {
1598                let buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
1599                let text = buffer_snapshot.text();
1600                let clamped_offset = offset.min(text.len());
1601                buffer_snapshot.offset_to_point(clamped_offset)
1602            });
1603            Self::focus_editor_and_center_on_point(&editor, point, window, cx);
1604        } else {
1605            let point = editor.update(cx, |editor, cx| {
1606                editor.move_to_end(&MoveToEnd, window, cx);
1607                editor.selections.newest(cx).head()
1608            });
1609            Self::focus_editor_and_center_on_point(&editor, point, window, cx);
1610        }
1611
1612        if preprocessing_applied {
1613            editor.update(cx, |editor, cx| {
1614                let current_selection: text::Selection<usize> =
1615                    editor.selections.newest(cx).clone();
1616                editor.set_text(preprocessed_text, window, cx);
1617                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1618                    s.select_ranges([current_selection.range()])
1619                });
1620            });
1621        }
1622        let (reg, ed) = (self.language_registry.clone(), editor.clone());
1623        cx.spawn(async move |_, cx| {
1624            if let Ok(md) = reg.language_for_name("Markdown").await {
1625                ed.update(cx, |e, cx| {
1626                    e.buffer().update(cx, |buf, cx| {
1627                        for b in buf.all_buffers() {
1628                            b.update(cx, |b, cx| {
1629                                b.set_language_registry(reg.clone());
1630                                b.set_language(Some(md.clone()), cx);
1631                            });
1632                        }
1633                    })
1634                })
1635                .ok();
1636            }
1637        })
1638        .detach();
1639
1640        let buffer_edited_subscription = cx.subscribe(&editor, |this, _, event, cx| {
1641            if matches!(event, EditorEvent::BufferEdited) {
1642                this.update_editing_message_token_count(true, cx);
1643            }
1644        });
1645
1646        let mut state =
1647            self.create_common_editing_state(message_id, editor.clone(), true, window, cx);
1648        state.preprocessing_applied = preprocessing_applied;
1649        state._subscriptions[0] = buffer_edited_subscription;
1650
1651        self.editing_message = Some((message_id, state));
1652        self.update_editing_message_token_count(false, cx);
1653        cx.notify();
1654    }
1655
1656    fn create_common_editing_state(
1657        &mut self,
1658        _message_id: MessageId,
1659        editor: Entity<Editor>,
1660        is_assistant_message: bool,
1661        window: &mut Window,
1662        cx: &mut Context<Self>,
1663    ) -> EditingMessageState {
1664        let handle = PopoverMenuHandle::default();
1665        let context_strip = cx.new(|cx| {
1666            ContextStrip::new(
1667                self.context_store.clone(),
1668                self.workspace.clone(),
1669                Some(self.thread_store.downgrade()),
1670                Some(self.text_thread_store.downgrade()),
1671                handle.clone(),
1672                SuggestContextKind::File,
1673                ModelUsageContext::Thread(self.thread.clone()),
1674                window,
1675                cx,
1676            )
1677        });
1678
1679        let context_strip_subscription =
1680            cx.subscribe_in(&context_strip, window, Self::handle_context_strip_event);
1681
1682        let editor_weak = editor.downgrade();
1683        let list_state_clone = self.list_state.clone();
1684        let editor_subscription = cx.subscribe_in(
1685            &editor,
1686            window,
1687            move |this: &mut ActiveThread, _editor, event: &EditorEvent, _window, cx| {
1688                if let EditorEvent::ScrollPositionChanged { .. } = event {
1689                    if let Some((_, editing_state)) = &this.editing_message {
1690                        if editing_state.is_agent_message {
1691                            if let Some(editor) = editor_weak.upgrade() {
1692                                // Sync panel scroll to editor scroll
1693                                let scroll_anchor = editor.read(cx).scroll_manager.anchor();
1694                                let editor_y = scroll_anchor.offset.y;
1695                                list_state_clone.scroll_to(ListOffset {
1696                                    item_ix: 0,
1697                                    offset_in_item: px(editor_y),
1698                                });
1699                                cx.notify();
1700                            }
1701                        }
1702                    }
1703                }
1704            },
1705        );
1706
1707        EditingMessageState {
1708            editor,
1709            is_agent_message: is_assistant_message,
1710            context_strip,
1711            context_picker_menu_handle: handle,
1712            last_estimated_token_count: None,
1713            _subscriptions: [editor_subscription, context_strip_subscription],
1714            _update_token_count_task: None,
1715            preprocessing_applied: false,
1716        }
1717    }
1718
1719    fn handle_context_strip_event(
1720        &mut self,
1721        _context_strip: &Entity<ContextStrip>,
1722        event: &ContextStripEvent,
1723        window: &mut Window,
1724        cx: &mut Context<Self>,
1725    ) {
1726        if let Some((_, state)) = self.editing_message.as_ref() {
1727            match event {
1728                ContextStripEvent::PickerDismissed
1729                | ContextStripEvent::BlurredEmpty
1730                | ContextStripEvent::BlurredDown => {
1731                    let editor_focus_handle = state.editor.focus_handle(cx);
1732                    window.focus(&editor_focus_handle);
1733                }
1734                ContextStripEvent::BlurredUp => {}
1735            }
1736        }
1737    }
1738
1739    fn focus_editor_and_center_on_point(
1740        editor: &Entity<Editor>,
1741        point: Point,
1742        window: &mut Window,
1743        cx: &mut Context<Self>,
1744    ) {
1745        editor.update(cx, |editor, cx| {
1746            let snapshot = editor.buffer().read(cx).snapshot(cx);
1747            let anchor = snapshot.anchor_before(point);
1748            editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
1749                s.select_ranges([anchor..anchor]);
1750            });
1751        });
1752
1753        // Autoscroll after text is set
1754        let editor_handle = editor.downgrade();
1755        cx.on_next_frame(window, move |_, window, cx| {
1756            if window.is_window_active() {
1757                if let Some(editor) = editor_handle.upgrade() {
1758                    editor.update(cx, |editor, cx| {
1759                        let snapshot = editor.buffer().read(cx).snapshot(cx);
1760                        let anchor = snapshot.anchor_before(point);
1761                        editor.change_selections(
1762                            SelectionEffects::scroll(Autoscroll::center()),
1763                            window,
1764                            cx,
1765                            |s| {
1766                                s.select_ranges([anchor..anchor]);
1767                            },
1768                        );
1769                    });
1770                }
1771            }
1772        });
1773    }
1774
1775    fn update_editing_message_token_count(&mut self, debounce: bool, cx: &mut Context<Self>) {
1776        let Some((message_id, state)) = self.editing_message.as_mut() else {
1777            return;
1778        };
1779
1780        cx.emit(ActiveThreadEvent::EditingMessageTokenCountChanged);
1781        state._update_token_count_task.take();
1782
1783        let Some(configured_model) = self.thread.read(cx).configured_model() else {
1784            state.last_estimated_token_count.take();
1785            return;
1786        };
1787
1788        let editor = state.editor.clone();
1789        let thread = self.thread.clone();
1790        let message_id = *message_id;
1791
1792        state._update_token_count_task = Some(cx.spawn(async move |this, cx| {
1793            if debounce {
1794                cx.background_executor()
1795                    .timer(Duration::from_millis(200))
1796                    .await;
1797            }
1798
1799            let token_count = if let Some(task) = cx
1800                .update(|cx| {
1801                    let Some(message) = thread.read(cx).message(message_id) else {
1802                        log::error!("Message that was being edited no longer exists");
1803                        return None;
1804                    };
1805                    let message_text = editor.read(cx).text(cx);
1806
1807                    if message_text.is_empty() && message.loaded_context.is_empty() {
1808                        return None;
1809                    }
1810
1811                    let mut request_message = LanguageModelRequestMessage {
1812                        role: language_model::Role::User,
1813                        content: Vec::new(),
1814                        cache: false,
1815                    };
1816
1817                    message
1818                        .loaded_context
1819                        .add_to_request_message(&mut request_message);
1820
1821                    if !message_text.is_empty() {
1822                        request_message
1823                            .content
1824                            .push(MessageContent::Text(message_text));
1825                    }
1826
1827                    let request = language_model::LanguageModelRequest {
1828                        thread_id: None,
1829                        prompt_id: None,
1830                        intent: None,
1831                        mode: None,
1832                        messages: vec![request_message],
1833                        tools: vec![],
1834                        tool_choice: None,
1835                        stop: vec![],
1836                        temperature: AgentSettings::temperature_for_model(
1837                            &configured_model.model,
1838                            cx,
1839                        ),
1840                        thinking_allowed: true,
1841                    };
1842
1843                    Some(configured_model.model.count_tokens(request, cx))
1844                })
1845                .ok()
1846                .flatten()
1847            {
1848                task.await.log_err()
1849            } else {
1850                Some(0)
1851            };
1852
1853            if let Some(token_count) = token_count {
1854                this.update(cx, |this, cx| {
1855                    let Some((_message_id, state)) = this.editing_message.as_mut() else {
1856                        return;
1857                    };
1858
1859                    state.last_estimated_token_count = Some(token_count);
1860                    cx.emit(ActiveThreadEvent::EditingMessageTokenCountChanged);
1861                })
1862                .ok();
1863            };
1864        }));
1865    }
1866
1867    fn toggle_context_picker(
1868        &mut self,
1869        _: &crate::ToggleContextPicker,
1870        window: &mut Window,
1871        cx: &mut Context<Self>,
1872    ) {
1873        if let Some((_, state)) = self.editing_message.as_mut() {
1874            let handle = state.context_picker_menu_handle.clone();
1875            window.defer(cx, move |window, cx| {
1876                handle.toggle(window, cx);
1877            });
1878        }
1879    }
1880
1881    fn remove_all_context(
1882        &mut self,
1883        _: &crate::RemoveAllContext,
1884        _window: &mut Window,
1885        cx: &mut Context<Self>,
1886    ) {
1887        self.context_store.update(cx, |store, cx| store.clear(cx));
1888        cx.notify();
1889    }
1890
1891    fn move_up(&mut self, _: &MoveUp, window: &mut Window, cx: &mut Context<Self>) {
1892        if let Some((_, state)) = self.editing_message.as_mut() {
1893            if state.context_picker_menu_handle.is_deployed() {
1894                cx.propagate();
1895            } else {
1896                state.context_strip.focus_handle(cx).focus(window);
1897            }
1898        }
1899    }
1900
1901    fn paste(&mut self, _: &Paste, _window: &mut Window, cx: &mut Context<Self>) {
1902        attach_pasted_images_as_context(&self.context_store, cx);
1903    }
1904
1905    fn cancel_editing_message(
1906        &mut self,
1907        _: &menu::Cancel,
1908        window: &mut Window,
1909        cx: &mut Context<Self>,
1910    ) {
1911        self.editing_message.take();
1912        cx.notify();
1913
1914        if let Some(workspace) = self.workspace.upgrade() {
1915            workspace.update(cx, |workspace, cx| {
1916                if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
1917                    panel.focus_handle(cx).focus(window);
1918                }
1919            });
1920        }
1921    }
1922
1923    fn confirm_editing_message(
1924        &mut self,
1925        _: &menu::Confirm,
1926        window: &mut Window,
1927        cx: &mut Context<Self>,
1928    ) {
1929        let Some((message_id, state)) = self.editing_message.take() else {
1930            return;
1931        };
1932
1933        let Some(model) = self
1934            .thread
1935            .update(cx, |thread, cx| thread.get_or_init_configured_model(cx))
1936        else {
1937            return;
1938        };
1939
1940        if model.provider.must_accept_terms(cx) {
1941            cx.notify();
1942            return;
1943        }
1944
1945        let edited_text = state.editor.read(cx).text(cx);
1946
1947        let restored_text = if state.preprocessing_applied {
1948            LANG_PREFIX_REGEX
1949                .replace_all(&edited_text, "```$2")
1950                .to_string()
1951        } else {
1952            edited_text
1953        };
1954
1955        let creases = state.editor.update(cx, extract_message_creases);
1956
1957        let new_context = self
1958            .context_store
1959            .read(cx)
1960            .new_context_for_thread(self.thread.read(cx), Some(message_id));
1961
1962        let project = self.thread.read(cx).project().clone();
1963        let prompt_store = self.thread_store.read(cx).prompt_store().clone();
1964
1965        let git_store = project.read(cx).git_store().clone();
1966        let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
1967
1968        let load_context_task = context::load_context(new_context, &project, &prompt_store, cx);
1969        self._load_edited_message_context_task =
1970            Some(cx.spawn_in(window, async move |this, cx| {
1971                let (context, checkpoint) =
1972                    futures::future::join(load_context_task, checkpoint).await;
1973                this.update_in(cx, |this, window, cx| {
1974                    let original_role = this
1975                        .thread
1976                        .read(cx)
1977                        .message(message_id)
1978                        .map(|m| m.role)
1979                        .unwrap_or(Role::User);
1980
1981                    this.thread.update(cx, |thread, cx| {
1982                        let segments = vec![MessageSegment::Text(restored_text)];
1983
1984                        thread.edit_message(
1985                            message_id,
1986                            original_role,
1987                            segments,
1988                            creases,
1989                            Some(context.loaded_context),
1990                            checkpoint.ok(),
1991                            cx,
1992                        );
1993                        if original_role == Role::User {
1994                            let messages_to_delete = this.messages_after(message_id).to_vec();
1995                            for msg_id in messages_to_delete {
1996                                thread.delete_message(msg_id, cx);
1997                            }
1998                            thread.advance_prompt_id();
1999                            thread.cancel_last_completion(Some(window.window_handle()), cx);
2000                            thread.send_to_model(
2001                                model.model,
2002                                CompletionIntent::UserPrompt,
2003                                Some(window.window_handle()),
2004                                cx,
2005                            );
2006                        }
2007                    });
2008                    this._load_edited_message_context_task = None;
2009                    cx.notify();
2010                })
2011                .log_err();
2012            }));
2013
2014        if let Some(workspace) = self.workspace.upgrade() {
2015            workspace.update(cx, |workspace, cx| {
2016                if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
2017                    panel.focus_handle(cx).focus(window);
2018                }
2019            });
2020        }
2021    }
2022
2023    fn messages_after(&self, message_id: MessageId) -> &[MessageId] {
2024        self.messages
2025            .iter()
2026            .position(|id| *id == message_id)
2027            .map(|index| &self.messages[index + 1..])
2028            .unwrap_or(&[])
2029    }
2030
2031    fn save_editing_message(
2032        &mut self,
2033        _: &menu::SaveEdit,
2034        window: &mut Window,
2035        cx: &mut Context<Self>,
2036    ) {
2037        let Some((message_id, state)) = self.editing_message.take() else {
2038            return;
2039        };
2040
2041        let edited_text = state.editor.read(cx).text(cx);
2042
2043        let new_context = self
2044            .context_store
2045            .read(cx)
2046            .new_context_for_thread(self.thread.read(cx), Some(message_id));
2047
2048        let project = self.thread.read(cx).project().clone();
2049        let prompt_store = self.thread_store.read(cx).prompt_store().clone();
2050
2051        let git_store = project.read(cx).git_store().clone();
2052        let checkpoint = git_store.update(cx, |git_store, cx| git_store.checkpoint(cx));
2053
2054        let creases = state.editor.update(cx, extract_message_creases);
2055
2056        let load_context_task = context::load_context(new_context, &project, &prompt_store, cx);
2057        self._load_edited_message_context_task =
2058            Some(cx.spawn_in(window, async move |this, cx| {
2059                let (context, checkpoint) =
2060                    futures::future::join(load_context_task, checkpoint).await;
2061                this.update_in(cx, |this, _window, cx| {
2062                    let original_role = this
2063                        .thread
2064                        .read(cx)
2065                        .message(message_id)
2066                        .map(|m| m.role)
2067                        .unwrap_or(Role::User);
2068
2069                    this.thread.update(cx, |thread, cx| {
2070                        thread.edit_message(
2071                            message_id,
2072                            original_role,
2073                            vec![MessageSegment::Text(edited_text)],
2074                            creases,
2075                            Some(context.loaded_context),
2076                            checkpoint.ok(),
2077                            cx,
2078                        );
2079                    });
2080                    this._load_edited_message_context_task = None;
2081                    cx.notify();
2082                })
2083                .log_err();
2084            }));
2085    }
2086
2087    fn handle_cancel_click(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
2088        self.cancel_editing_message(&menu::Cancel, window, cx);
2089    }
2090
2091    fn handle_save_click(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
2092        self.save_editing_message(&menu::SaveEdit, window, cx);
2093    }
2094
2095    fn handle_regenerate_click(
2096        &mut self,
2097        _: &ClickEvent,
2098        window: &mut Window,
2099        cx: &mut Context<Self>,
2100    ) {
2101        self.confirm_editing_message(&menu::Confirm, window, cx);
2102    }
2103
2104    fn handle_feedback_click(
2105        &mut self,
2106        message_id: MessageId,
2107        feedback: ThreadFeedback,
2108        window: &mut Window,
2109        cx: &mut Context<Self>,
2110    ) {
2111        let report = self.thread.update(cx, |thread, cx| {
2112            thread.report_message_feedback(message_id, feedback, cx)
2113        });
2114
2115        cx.spawn(async move |this, cx| {
2116            report.await?;
2117            this.update(cx, |_this, cx| cx.notify())
2118        })
2119        .detach_and_log_err(cx);
2120
2121        match feedback {
2122            ThreadFeedback::Positive => {
2123                self.open_feedback_editors.remove(&message_id);
2124            }
2125            ThreadFeedback::Negative => {
2126                self.handle_show_feedback_comments(message_id, window, cx);
2127            }
2128        }
2129    }
2130
2131    fn handle_show_feedback_comments(
2132        &mut self,
2133        message_id: MessageId,
2134        window: &mut Window,
2135        cx: &mut Context<Self>,
2136    ) {
2137        let buffer = cx.new(|cx| {
2138            let empty_string = String::new();
2139            MultiBuffer::singleton(cx.new(|cx| Buffer::local(empty_string, cx)), cx)
2140        });
2141
2142        let editor = cx.new(|cx| {
2143            let mut editor = Editor::new(
2144                editor::EditorMode::AutoHeight {
2145                    min_lines: 1,
2146                    max_lines: Some(4),
2147                },
2148                buffer,
2149                None,
2150                window,
2151                cx,
2152            );
2153            editor.set_placeholder_text(
2154                "What went wrong? Share your feedback so we can improve.",
2155                cx,
2156            );
2157            editor
2158        });
2159
2160        editor.read(cx).focus_handle(cx).focus(window);
2161        self.open_feedback_editors.insert(message_id, editor);
2162        cx.notify();
2163    }
2164
2165    fn submit_feedback_message(&mut self, message_id: MessageId, cx: &mut Context<Self>) {
2166        let Some(editor) = self.open_feedback_editors.get(&message_id) else {
2167            return;
2168        };
2169
2170        let report_task = self.thread.update(cx, |thread, cx| {
2171            thread.report_message_feedback(message_id, ThreadFeedback::Negative, cx)
2172        });
2173
2174        let comments = editor.read(cx).text(cx);
2175        if !comments.is_empty() {
2176            let thread_id = self.thread.read(cx).id().clone();
2177            let comments_value = String::from(comments.as_str());
2178
2179            let message_content = self
2180                .thread
2181                .read(cx)
2182                .message(message_id)
2183                .map(|msg| msg.to_string())
2184                .unwrap_or_default();
2185
2186            telemetry::event!(
2187                "Assistant Thread Feedback Comments",
2188                thread_id,
2189                message_id = message_id.as_usize(),
2190                message_content,
2191                comments = comments_value
2192            );
2193
2194            self.open_feedback_editors.remove(&message_id);
2195
2196            cx.spawn(async move |this, cx| {
2197                report_task.await?;
2198                this.update(cx, |_this, cx| cx.notify())
2199            })
2200            .detach_and_log_err(cx);
2201        }
2202    }
2203
2204    fn render_edit_message_editor(
2205        &self,
2206        state: &EditingMessageState,
2207        _window: &mut Window,
2208        cx: &Context<Self>,
2209    ) -> impl IntoElement {
2210        let settings = ThemeSettings::get_global(cx);
2211        let editor_settings = editor::EditorSettings::get_global(cx);
2212
2213        let font_size = TextSize::Small
2214            .rems(cx)
2215            .to_pixels(settings.agent_font_size(cx));
2216        let line_height = font_size * 1.75;
2217        let colors = cx.theme().colors();
2218
2219        let text_style = TextStyle {
2220            color: colors.text,
2221            font_family: settings.buffer_font.family.clone(),
2222            font_fallbacks: settings.buffer_font.fallbacks.clone(),
2223            font_features: settings.buffer_font.features.clone(),
2224            font_size: font_size.into(),
2225            line_height: line_height.into(),
2226            ..Default::default()
2227        };
2228
2229        let editor_style = EditorStyle {
2230            background: colors.editor_background,
2231            local_player: cx.theme().players().local(),
2232            text: text_style,
2233            syntax: cx.theme().syntax().clone(),
2234            ..Default::default()
2235        };
2236
2237        let editor_element = div()
2238            .pt(px(-3.))
2239            .when(
2240                state.is_agent_message && !editor_settings.gutter.line_numbers,
2241                |d| d.px_neg_0p5(),
2242            )
2243            .w_full()
2244            .child(EditorElement::new(&state.editor, editor_style))
2245            .into_any_element();
2246
2247        v_flex()
2248            .key_context("EditMessageEditor")
2249            .on_action(cx.listener(Self::toggle_context_picker))
2250            .on_action(cx.listener(Self::remove_all_context))
2251            .on_action(cx.listener(Self::move_up))
2252            .capture_action(cx.listener(Self::paste))
2253            .w_full()
2254            .gap_1()
2255            .when(state.is_agent_message, |container| {
2256                container
2257                    .capture_action(cx.listener(Self::cancel_editing_message))
2258                    .capture_action(cx.listener(Self::save_editing_message))
2259            })
2260            .when(!state.is_agent_message, |container| {
2261                container
2262                    .on_action(cx.listener(Self::cancel_editing_message))
2263                    .on_action(cx.listener(Self::confirm_editing_message))
2264                    .on_action(cx.listener(Self::save_editing_message))
2265                    .child(state.context_strip.clone())
2266            })
2267            .child(editor_element)
2268
2269        // v_flex()
2270        //     .key_context("EditMessageEditor")
2271        //     .on_action(cx.listener(Self::toggle_context_picker))
2272        //     .on_action(cx.listener(Self::remove_all_context))
2273        //     .on_action(cx.listener(Self::move_up))
2274        //     .on_action(cx.listener(Self::cancel_editing_message))
2275        //     .on_action(cx.listener(Self::confirm_editing_message))
2276        //     .capture_action(cx.listener(Self::paste))
2277        //     .min_h_6()
2278        //     .w_full()
2279        //     .flex_grow()
2280        //     .gap_2()
2281        //     .child(state.context_strip.clone())
2282        //     .child(div().pt(px(-3.)).px_neg_0p5().child(EditorElement::new(
2283        //         &state.editor,
2284        //         EditorStyle {
2285        //             background: colors.editor_background,
2286        //             local_player: cx.theme().players().local(),
2287        //             text: text_style,
2288        //             syntax: cx.theme().syntax().clone(),
2289        //             ..Default::default()
2290        //         },
2291        //     )))
2292    }
2293
2294    fn render_message(&self, ix: usize, window: &mut Window, cx: &mut Context<Self>) -> AnyElement {
2295        let message_id = self.messages[ix];
2296        let workspace = self.workspace.clone();
2297        let thread = self.thread.read(cx);
2298
2299        let is_first_message = ix == 0;
2300        let is_last_message = ix == self.messages.len() - 1;
2301
2302        let Some(message) = thread.message(message_id) else {
2303            return Empty.into_any();
2304        };
2305
2306        let is_generating = thread.is_generating();
2307        let is_generating_stale = thread.is_generation_stale().unwrap_or(false);
2308
2309        let loading_dots = (is_generating && is_last_message).then(|| {
2310            h_flex()
2311                .h_8()
2312                .my_3()
2313                .mx_5()
2314                .when(is_generating_stale || message.is_hidden, |this| {
2315                    this.child(LoadingLabel::new("").size(LabelSize::Small))
2316                })
2317        });
2318
2319        if message.is_hidden {
2320            return div().children(loading_dots).into_any();
2321        }
2322
2323        let Some(rendered_message) = self.rendered_messages_by_id.get(&message_id) else {
2324            return Empty.into_any();
2325        };
2326
2327        // Get all the data we need from thread before we start using it in closures
2328        let checkpoint = thread.checkpoint_for_message(message_id);
2329        let configured_model = thread.configured_model().map(|m| m.model);
2330        let added_context = thread
2331            .context_for_message(message_id)
2332            .map(|context| AddedContext::new_attached(context, configured_model.as_ref(), cx))
2333            .collect::<Vec<_>>();
2334
2335        let tool_uses = thread.tool_uses_for_message(message_id, cx);
2336        let has_tool_uses = !tool_uses.is_empty();
2337
2338        let editing_message_state = self
2339            .editing_message
2340            .as_ref()
2341            .filter(|(id, _)| *id == message_id)
2342            .map(|(_, state)| state);
2343
2344        let (editor_bg_color, panel_bg) = {
2345            let colors = cx.theme().colors();
2346            (colors.editor_background, colors.panel_background)
2347        };
2348
2349        let open_as_markdown = IconButton::new(("open-as-markdown", ix), IconName::DocumentText)
2350            .icon_size(IconSize::XSmall)
2351            .icon_color(Color::Ignored)
2352            .tooltip(Tooltip::text("Open Thread as Markdown"))
2353            .on_click({
2354                let thread = self.thread.clone();
2355                let workspace = self.workspace.clone();
2356                move |_, window, cx| {
2357                    if let Some(workspace) = workspace.upgrade() {
2358                        open_active_thread_as_markdown(thread.clone(), workspace, window, cx)
2359                            .detach_and_log_err(cx);
2360                    }
2361                }
2362            });
2363
2364        let scroll_to_top = IconButton::new(("scroll_to_top", ix), IconName::ArrowUpAlt)
2365            .icon_size(IconSize::XSmall)
2366            .icon_color(Color::Ignored)
2367            .tooltip(Tooltip::text("Scroll To Top"))
2368            .on_click(cx.listener(move |this, _, _, cx| {
2369                this.scroll_to_top(cx);
2370            }));
2371
2372        let show_feedback = thread.is_turn_end(ix);
2373        let feedback_container = h_flex()
2374            .group("feedback_container")
2375            .mt_1()
2376            .py_2()
2377            .px(RESPONSE_PADDING_X)
2378            .mr_1()
2379            .opacity(0.4)
2380            .hover(|style| style.opacity(1.))
2381            .gap_1p5()
2382            .flex_wrap()
2383            .justify_end();
2384        let feedback_items = match self.thread.read(cx).message_feedback(message_id) {
2385            Some(feedback) => feedback_container
2386                .child(
2387                    div().visible_on_hover("feedback_container").child(
2388                        Label::new(match feedback {
2389                            ThreadFeedback::Positive => "Thanks for your feedback!",
2390                            ThreadFeedback::Negative => {
2391                                "We appreciate your feedback and will use it to improve."
2392                            }
2393                        })
2394                    .color(Color::Muted)
2395                    .size(LabelSize::XSmall)
2396                    .truncate())
2397                )
2398                .child(
2399                    h_flex()
2400                        .child(
2401                            IconButton::new(("feedback-thumbs-up", ix), IconName::ThumbsUp)
2402                                .icon_size(IconSize::XSmall)
2403                                .icon_color(match feedback {
2404                                    ThreadFeedback::Positive => Color::Accent,
2405                                    ThreadFeedback::Negative => Color::Ignored,
2406                                })
2407                                .tooltip(Tooltip::text("Helpful Response"))
2408                                .on_click(cx.listener(move |this, _, window, cx| {
2409                                    this.handle_feedback_click(
2410                                        message_id,
2411                                        ThreadFeedback::Positive,
2412                                        window,
2413                                        cx,
2414                                    );
2415                                })),
2416                        )
2417                        .child(
2418                            IconButton::new(("feedback-thumbs-down", ix), IconName::ThumbsDown)
2419                                .icon_size(IconSize::XSmall)
2420                                .icon_color(match feedback {
2421                                    ThreadFeedback::Positive => Color::Ignored,
2422                                    ThreadFeedback::Negative => Color::Accent,
2423                                })
2424                                .tooltip(Tooltip::text("Not Helpful"))
2425                                .on_click(cx.listener(move |this, _, window, cx| {
2426                                    this.handle_feedback_click(
2427                                        message_id,
2428                                        ThreadFeedback::Negative,
2429                                        window,
2430                                        cx,
2431                                    );
2432                                })),
2433                        )
2434                        .child(open_as_markdown),
2435                )
2436                .into_any_element(),
2437            None if AgentSettings::get_global(cx).enable_feedback =>
2438                feedback_container
2439                .child(
2440                    div().visible_on_hover("feedback_container").child(
2441                        Label::new(
2442                            "Rating the thread sends all of your current conversation to the Zed team.",
2443                        )
2444                        .color(Color::Muted)
2445                    .size(LabelSize::XSmall)
2446                    .truncate())
2447                )
2448                .child(
2449                    h_flex()
2450                        .child(
2451                            IconButton::new(("feedback-thumbs-up", ix), IconName::ThumbsUp)
2452                                .icon_size(IconSize::XSmall)
2453                                .icon_color(Color::Ignored)
2454                                .tooltip(Tooltip::text("Helpful Response"))
2455                                .on_click(cx.listener(move |this, _, window, cx| {
2456                                    this.handle_feedback_click(
2457                                        message_id,
2458                                        ThreadFeedback::Positive,
2459                                        window,
2460                                        cx,
2461                                    );
2462                                })),
2463                        )
2464                        .child(
2465                            IconButton::new(("feedback-thumbs-down", ix), IconName::ThumbsDown)
2466                                .icon_size(IconSize::XSmall)
2467                                .icon_color(Color::Ignored)
2468                                .tooltip(Tooltip::text("Not Helpful"))
2469                                .on_click(cx.listener(move |this, _, window, cx| {
2470                                    this.handle_feedback_click(
2471                                        message_id,
2472                                        ThreadFeedback::Negative,
2473                                        window,
2474                                        cx,
2475                                    );
2476                                })),
2477                        )
2478                        .child(open_as_markdown)
2479                        .child(scroll_to_top),
2480                )
2481                .into_any_element(),
2482            None => feedback_container
2483                .child(h_flex()
2484                    .child(open_as_markdown))
2485                    .child(scroll_to_top)
2486                .into_any_element(),
2487        };
2488
2489        let message_is_empty = message.should_display_content();
2490        let has_content = !message_is_empty || !added_context.is_empty();
2491
2492        let message_content = has_content.then(|| {
2493            if let Some(state) = editing_message_state.as_ref() {
2494                self.render_edit_message_editor(state, window, cx)
2495                    .into_any_element()
2496            } else {
2497                let base_content_element = v_flex()
2498                    .w_full()
2499                    .gap_1()
2500                    .when(!added_context.is_empty(), |parent| {
2501                        parent.child(h_flex().flex_wrap().gap_1().children(
2502                            added_context.into_iter().map(|added_context| {
2503                                let context = added_context.handle.clone();
2504                                ContextPill::added(added_context, false, false, None).on_click(
2505                                    Rc::new(cx.listener({
2506                                        let workspace = workspace.clone();
2507                                        move |_, _, window, cx| {
2508                                            if let Some(workspace) = workspace.upgrade() {
2509                                                open_context(&context, workspace, window, cx);
2510                                                cx.notify();
2511                                            }
2512                                        }
2513                                    })),
2514                                )
2515                            }),
2516                        ))
2517                    })
2518                    .when(!message_is_empty, |parent| {
2519                        parent.child(
2520                            div().pt_0p5().min_h_6().child(
2521                                self.render_message_content(
2522                                    message_id,
2523                                    rendered_message,
2524                                    has_tool_uses,
2525                                    workspace.clone(),
2526                                    window,
2527                                    cx,
2528                                )
2529                                .into_any_element(),
2530                            ),
2531                        )
2532                    });
2533
2534                if message.role == Role::Assistant && editing_message_state.is_none() {
2535                    base_content_element
2536                        .id(("assistant_content_wrapper", ix))
2537                        .into_any_element()
2538                } else {
2539                    base_content_element.into_any_element()
2540                }
2541            }
2542        });
2543
2544        let styled_message = if message.ui_only {
2545            self.render_ui_notification(message_content, ix, cx)
2546        } else {
2547            match message.role {
2548                Role::User => {
2549                    let colors = cx.theme().colors();
2550                    v_flex()
2551                        .id(("message-container", ix))
2552                        .pt_2()
2553                        .pl_2()
2554                        .pr_2p5()
2555                        .pb_4()
2556                        .child(
2557                            v_flex()
2558                                .id(("user-message", ix))
2559                                .bg(editor_bg_color)
2560                                .rounded_lg()
2561                                .shadow_md()
2562                                .border_1()
2563                                .border_color(colors.border)
2564                                .hover(|hover| hover.border_color(colors.text_accent.opacity(0.5)))
2565                                .child(
2566                                    v_flex()
2567                                        .p_2p5()
2568                                        .gap_1()
2569                                        .children(message_content)
2570                                        .when_some(editing_message_state, |this, state| {
2571                                            let focus_handle = state.editor.focus_handle(cx).clone();
2572
2573                                            this.child(
2574                                                h_flex()
2575                                                    .w_full()
2576                                                    .gap_1()
2577                                                    .justify_between()
2578                                                    .flex_wrap()
2579                                                    .child(
2580                                                        h_flex()
2581                                                            .gap_1p5()
2582                                                            .child(
2583                                                                div()
2584                                                                    .opacity(0.8)
2585                                                                    .child(
2586                                                                        Icon::new(IconName::Warning)
2587                                                                            .size(IconSize::Indicator)
2588                                                                            .color(Color::Warning)
2589                                                                    ),
2590                                                            )
2591                                                            .child(
2592                                                                Label::new("Editing will restart the thread from this point.")
2593                                                                    .color(Color::Muted)
2594                                                                    .size(LabelSize::XSmall),
2595                                                            ),
2596                                                    )
2597                                                    .child(
2598                                                        h_flex()
2599                                                            .gap_0p5()
2600                                                            .child(
2601                                                                IconButton::new(
2602                                                                    "cancel-edit-message",
2603                                                                    IconName::Close,
2604                                                                )
2605                                                                .shape(ui::IconButtonShape::Square)
2606                                                                .icon_color(Color::Error)
2607                                                                .icon_size(IconSize::Small)
2608                                                                .tooltip({
2609                                                                    let focus_handle = focus_handle.clone();
2610                                                                    move |window, cx| {
2611                                                                        Tooltip::for_action_in(
2612                                                                            "Cancel Edit",
2613                                                                            &menu::Cancel,
2614                                                                            &focus_handle,
2615                                                                            window,
2616                                                                            cx,
2617                                                                        )
2618                                                                    }
2619                                                                })
2620                                                                .on_click(cx.listener(Self::handle_cancel_click)),
2621                                                            )
2622                                                            .child(
2623                                                                IconButton::new(
2624                                                                    "save-edit-message",
2625                                                                    IconName::Save,
2626                                                                )
2627                                                                .disabled(state.editor.read(cx).is_empty(cx))
2628                                                                .shape(ui::IconButtonShape::Square)
2629                                                                .icon_color(Color::Muted)
2630                                                                .icon_size(IconSize::Small)
2631                                                                // .tooltip({
2632                                                                //     let focus_handle = focus_handle.clone();
2633                                                                //     move |window, cx| {
2634                                                                //         Tooltip::for_action_in(
2635                                                                //             "Only Save",
2636                                                                //             &menu::SaveEdit,
2637                                                                //             &focus_handle,
2638                                                                //             window,
2639                                                                //             cx,
2640                                                                //         )
2641                                                                //     }
2642                                                                // })
2643                                                                .on_click(cx.listener(Self::handle_save_click)),
2644                                                            )
2645                                                            .child(
2646                                                                IconButton::new(
2647                                                                    "confirm-edit-message",
2648                                                                    IconName::Return,
2649                                                                )
2650                                                                .disabled(state.editor.read(cx).is_empty(cx))
2651                                                                .shape(ui::IconButtonShape::Square)
2652                                                                .icon_color(Color::Muted)
2653                                                                .icon_size(IconSize::Small)
2654                                                                .tooltip({
2655                                                                    let focus_handle = focus_handle.clone();
2656                                                                    move |window, cx| {
2657                                                                        Tooltip::for_action_in(
2658                                                                            "Regenerate",
2659                                                                            &menu::Confirm,
2660                                                                            &focus_handle,
2661                                                                            window,
2662                                                                            cx,
2663                                                                        )
2664                                                                    }
2665                                                                })
2666                                                                .on_click(
2667                                                                    cx.listener(Self::handle_regenerate_click),
2668                                                                ),
2669                                                            ),
2670                                                    )
2671                                            )
2672                                        }),
2673                                )
2674                                        .on_mouse_down(MouseButton::Left, |_, window, _| window.prevent_default())
2675                                // .on_click(cx.listener({
2676                                //     let message_creases = message.creases.clone();
2677                                //     move |this, _, window, cx| {
2678                                //         if let Some(message_text) =
2679                                //             this.thread.read(cx).message(message_id).and_then(|message| {
2680                                //                 message.segments.first().and_then(|segment| {
2681                                //                     match segment {
2682                                //                         MessageSegment::Text(message_text) => {
2683                                //                             Some(Into::<Arc<str>>::into(message_text.as_str()))
2684                                //                         }
2685                                //                         _ => {
2686                                //                             None
2687                                //                         }
2688                                //                     }
2689                                //                 })
2690                                //             })
2691                                //         {
2692                                //             this.start_editing_message(
2693                                //                 message_id,
2694                                //                 message_text,
2695                                //                 &message_creases,
2696                                //                 window,
2697                                //                 cx,
2698                                //             );
2699                                //         }
2700                                //     }
2701                                // })),
2702                        )
2703                }
2704                Role::Assistant => v_flex()
2705                    .id(("message-container", ix))
2706                    .px(RESPONSE_PADDING_X)
2707                    .gap_2()
2708                    .children(message_content)
2709                    .when(has_tool_uses, |parent| {
2710                        parent.children(tool_uses.into_iter().map(|tool_use| {
2711                            self.render_tool_use(tool_use, window, workspace.clone(), cx)
2712                        }))
2713                    })
2714                    .when_some(editing_message_state, |this, state| {
2715                        let focus_handle = state.editor.focus_handle(cx);
2716                        this.child(
2717                            h_flex()
2718                                .justify_end()
2719                                .gap_0p5()
2720                                .child(
2721                                    IconButton::new("cancel-edit-message", IconName::Close)
2722                                        .shape(ui::IconButtonShape::Square)
2723                                        .icon_color(Color::Error)
2724                                        .icon_size(IconSize::Small)
2725                                        .tooltip({
2726                                            let focus_handle = focus_handle.clone();
2727                                            move |window, cx| {
2728                                                Tooltip::for_action_in(
2729                                                    "Cancel Edit",
2730                                                    &menu::Cancel,
2731                                                    &focus_handle,
2732                                                    window,
2733                                                    cx,
2734                                                )
2735                                            }
2736                                        })
2737                                        .on_click(cx.listener(Self::handle_cancel_click)),
2738                                )
2739                                .child(
2740                                    IconButton::new("save-edit-message", IconName::Save)
2741                                        .disabled(state.editor.read(cx).is_empty(cx))
2742                                        .shape(ui::IconButtonShape::Square)
2743                                        .icon_color(Color::Muted)
2744                                        .icon_size(IconSize::Small)
2745                                        .tooltip({
2746                                            let focus_handle = focus_handle.clone();
2747                                            move |window, cx| {
2748                                                Tooltip::for_action_in(
2749                                                    "Save Edit",
2750                                                    &menu::SaveEdit,
2751                                                    &focus_handle,
2752                                                    window,
2753                                                    cx,
2754                                                )
2755                                            }
2756                                        })
2757                                        .on_click(cx.listener(Self::handle_save_click)),
2758                                ),
2759                        )
2760                    }),
2761                Role::System => {
2762                    let colors = cx.theme().colors();
2763                    div().id(("message-container", ix)).py_1().px_2().child(
2764                        v_flex()
2765                            .bg(colors.editor_background)
2766                            .rounded_sm()
2767                            .child(div().p_4().children(message_content))
2768                            .when_some(editing_message_state, |this, state| {
2769                                let focus_handle = state.editor.focus_handle(cx);
2770                                this.child(
2771                                    h_flex()
2772                                        .justify_end()
2773                                        .gap_0p5()
2774                                        .child(
2775                                            IconButton::new("cancel-edit-message", IconName::Close)
2776                                                .shape(ui::IconButtonShape::Square)
2777                                                .icon_color(Color::Error)
2778                                                .icon_size(IconSize::Small)
2779                                                .tooltip({
2780                                                    let focus_handle = focus_handle.clone();
2781                                                    move |window, cx| {
2782                                                        Tooltip::for_action_in(
2783                                                            "Cancel Edit",
2784                                                            &menu::Cancel,
2785                                                            &focus_handle,
2786                                                            window,
2787                                                            cx,
2788                                                        )
2789                                                    }
2790                                                })
2791                                                .on_click(cx.listener(Self::handle_cancel_click)),
2792                                        )
2793                                        .child(
2794                                            IconButton::new("save-edit-message", IconName::Save)
2795                                                .disabled(state.editor.read(cx).is_empty(cx))
2796                                                .shape(ui::IconButtonShape::Square)
2797                                                .icon_color(Color::Accent)
2798                                                .icon_size(IconSize::Small)
2799                                                .tooltip({
2800                                                    let focus_handle = focus_handle.clone();
2801                                                    move |window, cx| {
2802                                                        Tooltip::for_action_in(
2803                                                            "Save Edit",
2804                                                            &menu::SaveEdit,
2805                                                            &focus_handle,
2806                                                            window,
2807                                                            cx,
2808                                                        )
2809                                                    }
2810                                                })
2811                                                .on_click(cx.listener(Self::handle_save_click)),
2812                                        ),
2813                                )
2814                            }),
2815                    )
2816                }
2817            }
2818        };
2819
2820        let after_editing_message = self
2821            .editing_message
2822            .as_ref()
2823            .map_or(false, |(editing_message_id, _)| {
2824                message_id > *editing_message_id
2825            });
2826
2827        let backdrop = div()
2828            .id(("backdrop", ix))
2829            .size_full()
2830            .absolute()
2831            .inset_0()
2832            .bg(panel_bg)
2833            .opacity(0.8)
2834            .block_mouse_except_scroll()
2835            .on_click(cx.listener(Self::handle_cancel_click));
2836
2837        v_flex()
2838            .w_full()
2839            .map(|parent| {
2840                if let Some(checkpoint) = checkpoint.filter(|_| !is_generating) {
2841                    let mut is_pending = false;
2842                    let mut error = None;
2843                    if let Some(last_restore_checkpoint) =
2844                        self.thread.read(cx).last_restore_checkpoint()
2845                    {
2846                        if last_restore_checkpoint.message_id() == message_id {
2847                            match last_restore_checkpoint {
2848                                LastRestoreCheckpoint::Pending { .. } => is_pending = true,
2849                                LastRestoreCheckpoint::Error { error: err, .. } => {
2850                                    error = Some(err.clone());
2851                                }
2852                            }
2853                        }
2854                    }
2855
2856                    let restore_checkpoint_button =
2857                        Button::new(("restore-checkpoint", ix), "Restore Checkpoint")
2858                            .icon(if error.is_some() {
2859                                IconName::XCircle
2860                            } else {
2861                                IconName::Undo
2862                            })
2863                            .icon_size(IconSize::XSmall)
2864                            .icon_position(IconPosition::Start)
2865                            .icon_color(if error.is_some() {
2866                                Some(Color::Error)
2867                            } else {
2868                                None
2869                            })
2870                            .label_size(LabelSize::XSmall)
2871                            .disabled(is_pending)
2872                            .on_click(cx.listener(move |this, _, _window, cx| {
2873                                this.thread.update(cx, |thread, cx| {
2874                                    thread
2875                                        .restore_checkpoint(checkpoint.clone(), cx)
2876                                        .detach_and_log_err(cx);
2877                                });
2878                            }));
2879
2880                    let restore_checkpoint_button = if is_pending {
2881                        restore_checkpoint_button
2882                            .with_animation(
2883                                ("pulsating-restore-checkpoint-button", ix),
2884                                Animation::new(Duration::from_secs(2))
2885                                    .repeat()
2886                                    .with_easing(pulsating_between(0.6, 1.)),
2887                                |label, delta| label.alpha(delta),
2888                            )
2889                            .into_any_element()
2890                    } else if let Some(error) = error {
2891                        restore_checkpoint_button
2892                            .tooltip(Tooltip::text(error.to_string()))
2893                            .into_any_element()
2894                    } else {
2895                        restore_checkpoint_button.into_any_element()
2896                    };
2897
2898                    parent.child(
2899                        h_flex()
2900                            .pt_2p5()
2901                            .px_2p5()
2902                            .w_full()
2903                            .gap_1()
2904                            .child(ui::Divider::horizontal())
2905                            .child(restore_checkpoint_button)
2906                            .child(ui::Divider::horizontal()),
2907                    )
2908                } else {
2909                    parent
2910                }
2911            })
2912            .when(is_first_message, |parent| {
2913                parent.child(self.render_rules_item(cx))
2914            })
2915            .child(styled_message)
2916            .children(loading_dots)
2917            .when(show_feedback, move |parent| {
2918                parent.child(feedback_items).when_some(
2919                    self.open_feedback_editors.get(&message_id),
2920                    move |parent, feedback_editor| {
2921                        let focus_handle = feedback_editor.focus_handle(cx);
2922                        parent.child(
2923                            v_flex()
2924                                .key_context("AgentFeedbackMessageEditor")
2925                                .on_action(cx.listener(move |this, _: &menu::Cancel, _, cx| {
2926                                    this.open_feedback_editors.remove(&message_id);
2927                                    cx.notify();
2928                                }))
2929                                .on_action(cx.listener(move |this, _: &menu::Confirm, _, cx| {
2930                                    this.submit_feedback_message(message_id, cx);
2931                                    cx.notify();
2932                                }))
2933                                .on_action(cx.listener(Self::confirm_editing_message))
2934                                .mb_2()
2935                                .mx_4()
2936                                .p_2()
2937                                .rounded_md()
2938                                .border_1()
2939                                .border_color(cx.theme().colors().border)
2940                                .bg(cx.theme().colors().editor_background)
2941                                .child(feedback_editor.clone())
2942                                .child(
2943                                    h_flex()
2944                                        .gap_1()
2945                                        .justify_end()
2946                                        .child(
2947                                            Button::new("dismiss-feedback-message", "Cancel")
2948                                                .label_size(LabelSize::Small)
2949                                                .key_binding(
2950                                                    KeyBinding::for_action_in(
2951                                                        &menu::Cancel,
2952                                                        &focus_handle,
2953                                                        window,
2954                                                        cx,
2955                                                    )
2956                                                    .map(|kb| kb.size(rems_from_px(10.))),
2957                                                )
2958                                                .on_click(cx.listener(
2959                                                    move |this, _, _window, cx| {
2960                                                        this.open_feedback_editors
2961                                                            .remove(&message_id);
2962                                                        cx.notify();
2963                                                    },
2964                                                )),
2965                                        )
2966                                        .child(
2967                                            Button::new(
2968                                                "submit-feedback-message",
2969                                                "Share Feedback",
2970                                            )
2971                                            .style(ButtonStyle::Tinted(ui::TintColor::Accent))
2972                                            .label_size(LabelSize::Small)
2973                                            .key_binding(
2974                                                KeyBinding::for_action_in(
2975                                                    &menu::Confirm,
2976                                                    &focus_handle,
2977                                                    window,
2978                                                    cx,
2979                                                )
2980                                                .map(|kb| kb.size(rems_from_px(10.))),
2981                                            )
2982                                            .on_click(
2983                                                cx.listener(move |this, _, _window, cx| {
2984                                                    this.submit_feedback_message(message_id, cx);
2985                                                    cx.notify()
2986                                                }),
2987                                            ),
2988                                        ),
2989                                ),
2990                        )
2991                    },
2992                )
2993            })
2994            .when(after_editing_message, |parent| {
2995                // Backdrop to dim out the whole thread below the editing user message
2996                parent.relative().child(backdrop)
2997            })
2998            .into_any()
2999    }
3000
3001    fn render_message_content(
3002        &self,
3003        message_id: MessageId,
3004        rendered_message: &RenderedMessage,
3005        has_tool_uses: bool,
3006        workspace: WeakEntity<Workspace>,
3007        window: &Window,
3008        cx: &Context<Self>,
3009    ) -> impl IntoElement {
3010        let is_last_message = self.messages.last() == Some(&message_id);
3011        let is_generating = self.thread.read(cx).is_generating();
3012        let pending_thinking_segment_index = if is_generating && is_last_message && !has_tool_uses {
3013            rendered_message
3014                .segments
3015                .iter()
3016                .enumerate()
3017                .next_back()
3018                .filter(|(_, segment)| matches!(segment, RenderedMessageSegment::Thinking { .. }))
3019                .map(|(index, _)| index)
3020        } else {
3021            None
3022        };
3023
3024        let message_role = self
3025            .thread
3026            .read(cx)
3027            .message(message_id)
3028            .map(|m| m.role)
3029            .unwrap_or(Role::User);
3030
3031        let is_assistant_message = message_role == Role::Assistant;
3032        let is_user_message = message_role == Role::User;
3033
3034        v_flex()
3035            .text_ui(cx)
3036            .gap_2()
3037            .when(is_user_message, |this| this.text_xs())
3038            .children(
3039                rendered_message.segments.iter().enumerate().map(
3040                    |(index, segment)| match segment {
3041                        RenderedMessageSegment::Thinking {
3042                            content,
3043                            scroll_handle,
3044                        } => self
3045                            .render_message_thinking_segment(
3046                                message_id,
3047                                index,
3048                                content.clone(),
3049                                &scroll_handle,
3050                                Some(index) == pending_thinking_segment_index,
3051                                window,
3052                                cx,
3053                            )
3054                            .into_any_element(),
3055                        RenderedMessageSegment::Text(markdown) => {
3056                            let markdown_element = MarkdownElement::new(
3057                                markdown.clone(),
3058                                if is_user_message {
3059                                    let mut style = default_markdown_style(window, cx);
3060                                    let mut text_style = window.text_style();
3061                                    let theme_settings = ThemeSettings::get_global(cx);
3062
3063                                    let buffer_font = theme_settings.buffer_font.family.clone();
3064                                    let buffer_font_size = TextSize::Small.rems(cx);
3065
3066                                    text_style.refine(&TextStyleRefinement {
3067                                        font_family: Some(buffer_font),
3068                                        font_size: Some(buffer_font_size.into()),
3069                                        ..Default::default()
3070                                    });
3071
3072                                    style.base_text_style = text_style;
3073                                    style
3074                                } else {
3075                                    default_markdown_style(window, cx)
3076                                },
3077                            );
3078
3079                            let markdown_element = if is_assistant_message {
3080                                markdown_element.code_block_renderer(
3081                                    markdown::CodeBlockRenderer::Custom {
3082                                        render: Arc::new({
3083                                            let workspace = workspace.clone();
3084                                            let active_thread = cx.entity();
3085                                            move |kind,
3086                                                  parsed_markdown,
3087                                                  range,
3088                                                  metadata,
3089                                                  window,
3090                                                  cx| {
3091                                                render_markdown_code_block(
3092                                                    message_id,
3093                                                    range.start,
3094                                                    kind,
3095                                                    parsed_markdown,
3096                                                    metadata,
3097                                                    active_thread.clone(),
3098                                                    workspace.clone(),
3099                                                    window,
3100                                                    cx,
3101                                                )
3102                                            }
3103                                        }),
3104                                        transform: Some(Arc::new({
3105                                            let active_thread = cx.entity();
3106
3107                                            move |element, range, _, _, cx| {
3108                                                let is_expanded = active_thread
3109                                                    .read(cx)
3110                                                    .is_codeblock_expanded(message_id, range.start);
3111
3112                                                if is_expanded {
3113                                                    return element;
3114                                                }
3115
3116                                                element
3117                                            }
3118                                        })),
3119                                    },
3120                                )
3121                            } else {
3122                                markdown_element.code_block_renderer(
3123                                    markdown::CodeBlockRenderer::Default {
3124                                        copy_button: false,
3125                                        copy_button_on_hover: false,
3126                                        border: true,
3127                                    },
3128                                )
3129                            };
3130
3131                            let markdown_element = markdown_element.on_url_click({
3132                                let workspace = self.workspace.clone();
3133                                move |text, window, cx| {
3134                                    open_markdown_link(text, workspace.clone(), window, cx);
3135                                }
3136                            });
3137
3138                            if is_assistant_message {
3139                                let active_thread = cx.entity();
3140                                let markdown_element =
3141                                    markdown_element.on_click(move |text_offset, window, cx| {
3142                                        if window.modifiers().secondary() {
3143                                            active_thread.update(cx, |this, cx| {
3144                                                this.start_editing_assistant_message_at_segment(
3145                                                    message_id,
3146                                                    index,
3147                                                    text_offset,
3148                                                    window,
3149                                                    cx,
3150                                                );
3151                                            });
3152                                        }
3153                                    });
3154
3155                                div()
3156                                    .id(("assistant-message-text", message_id.as_usize()))
3157                                    .child(markdown_element)
3158                                    .into_any_element()
3159                            } else {
3160                                let active_thread = cx.entity();
3161                                let markdown_element =
3162                                    markdown_element.on_click(move |text_offset, window, cx| {
3163                                        let message_data = active_thread
3164                                            .read(cx)
3165                                            .thread
3166                                            .read(cx)
3167                                            .message(message_id)
3168                                            .map(|m| (m.segments.clone(), m.creases.clone()));
3169
3170                                        if let Some((segments, creases)) = message_data {
3171                                            if let Some(message_text) =
3172                                                segments.first().and_then(|segment| match segment {
3173                                                    MessageSegment::Text(message_text) => {
3174                                                        Some(Into::<Arc<str>>::into(
3175                                                            message_text.as_str(),
3176                                                        ))
3177                                                    }
3178                                                    _ => None,
3179                                                })
3180                                            {
3181                                                active_thread.update(cx, |this, inner_cx| {
3182                                                    this.start_editing_user_message(
3183                                                        message_id,
3184                                                        message_text,
3185                                                        &creases,
3186                                                        Some(text_offset),
3187                                                        window,
3188                                                        inner_cx,
3189                                                    );
3190                                                });
3191                                            }
3192                                        }
3193                                    });
3194                                div().child(markdown_element).into_any_element()
3195                            }
3196                        }
3197                    },
3198                ),
3199            )
3200    }
3201
3202    fn tool_card_border_color(&self, cx: &Context<Self>) -> Hsla {
3203        cx.theme().colors().border.opacity(0.5)
3204    }
3205
3206    fn tool_card_header_bg(&self, cx: &Context<Self>) -> Hsla {
3207        cx.theme()
3208            .colors()
3209            .element_background
3210            .blend(cx.theme().colors().editor_foreground.opacity(0.025))
3211    }
3212
3213    fn render_ui_notification(
3214        &self,
3215        message_content: impl IntoIterator<Item = impl IntoElement>,
3216        ix: usize,
3217        cx: &mut Context<Self>,
3218    ) -> Stateful<Div> {
3219        let message = div()
3220            .flex_1()
3221            .min_w_0()
3222            .text_size(TextSize::XSmall.rems(cx))
3223            .text_color(cx.theme().colors().text_muted)
3224            .children(message_content);
3225
3226        div()
3227            .id(("message-container", ix))
3228            .py_1()
3229            .px_2p5()
3230            .child(Banner::new().severity(ui::Severity::Warning).child(message))
3231    }
3232
3233    fn render_message_thinking_segment(
3234        &self,
3235        message_id: MessageId,
3236        ix: usize,
3237        markdown: Entity<Markdown>,
3238        scroll_handle: &ScrollHandle,
3239        pending: bool,
3240        window: &Window,
3241        cx: &Context<Self>,
3242    ) -> impl IntoElement {
3243        let is_open = self
3244            .expanded_thinking_segments
3245            .get(&(message_id, ix))
3246            .copied()
3247            .unwrap_or_default();
3248
3249        let editor_bg = cx.theme().colors().panel_background;
3250
3251        div().map(|this| {
3252            if pending {
3253                this.v_flex()
3254                    .mt_neg_2()
3255                    .mb_1p5()
3256                    .child(
3257                        h_flex()
3258                            .group("disclosure-header")
3259                            .justify_between()
3260                            .child(
3261                                h_flex()
3262                                    .gap_1p5()
3263                                    .child(
3264                                        Icon::new(IconName::ToolBulb)
3265                                            .size(IconSize::Small)
3266                                            .color(Color::Muted),
3267                                    )
3268                                    .child(LoadingLabel::new("Thinking").size(LabelSize::Small)),
3269                            )
3270                            .child(
3271                                h_flex()
3272                                    .gap_1()
3273                                    .child(
3274                                        div().visible_on_hover("disclosure-header").child(
3275                                            Disclosure::new("thinking-disclosure", is_open)
3276                                                .opened_icon(IconName::ChevronUp)
3277                                                .closed_icon(IconName::ChevronDown)
3278                                                .on_click(cx.listener({
3279                                                    move |this, _event, _window, _cx| {
3280                                                        let is_open = this
3281                                                            .expanded_thinking_segments
3282                                                            .entry((message_id, ix))
3283                                                            .or_insert(false);
3284
3285                                                        *is_open = !*is_open;
3286                                                    }
3287                                                })),
3288                                        ),
3289                                    )
3290                                    .child({
3291                                        Icon::new(IconName::ArrowCircle)
3292                                            .color(Color::Accent)
3293                                            .size(IconSize::Small)
3294                                            .with_animation(
3295                                                "arrow-circle",
3296                                                Animation::new(Duration::from_secs(2)).repeat(),
3297                                                |icon, delta| {
3298                                                    icon.transform(Transformation::rotate(
3299                                                        percentage(delta),
3300                                                    ))
3301                                                },
3302                                            )
3303                                    }),
3304                            ),
3305                    )
3306                    .when(!is_open, |this| {
3307                        let gradient_overlay = div()
3308                            .rounded_b_lg()
3309                            .h_full()
3310                            .absolute()
3311                            .w_full()
3312                            .bottom_0()
3313                            .left_0()
3314                            .bg(linear_gradient(
3315                                180.,
3316                                linear_color_stop(editor_bg, 1.),
3317                                linear_color_stop(editor_bg.opacity(0.2), 0.),
3318                            ));
3319
3320                        this.child(
3321                            div()
3322                                .relative()
3323                                .bg(editor_bg)
3324                                .rounded_b_lg()
3325                                .mt_2()
3326                                .pl_4()
3327                                .child(
3328                                    div()
3329                                        .id(("thinking-content", ix))
3330                                        .max_h_20()
3331                                        .track_scroll(scroll_handle)
3332                                        .text_ui_sm(cx)
3333                                        .overflow_hidden()
3334                                        .child(
3335                                            MarkdownElement::new(
3336                                                markdown.clone(),
3337                                                default_markdown_style(window, cx),
3338                                            )
3339                                            .on_url_click({
3340                                                let workspace = self.workspace.clone();
3341                                                move |text, window, cx| {
3342                                                    open_markdown_link(
3343                                                        text,
3344                                                        workspace.clone(),
3345                                                        window,
3346                                                        cx,
3347                                                    );
3348                                                }
3349                                            }),
3350                                        ),
3351                                )
3352                                .child(gradient_overlay),
3353                        )
3354                    })
3355                    .when(is_open, |this| {
3356                        this.child(
3357                            div()
3358                                .id(("thinking-content", ix))
3359                                .h_full()
3360                                .bg(editor_bg)
3361                                .text_ui_sm(cx)
3362                                .child(
3363                                    MarkdownElement::new(
3364                                        markdown.clone(),
3365                                        default_markdown_style(window, cx),
3366                                    )
3367                                    .on_url_click({
3368                                        let workspace = self.workspace.clone();
3369                                        move |text, window, cx| {
3370                                            open_markdown_link(text, workspace.clone(), window, cx);
3371                                        }
3372                                    }),
3373                                ),
3374                        )
3375                    })
3376            } else {
3377                this.v_flex()
3378                    .mt_neg_2()
3379                    .child(
3380                        h_flex()
3381                            .group("disclosure-header")
3382                            .pr_1()
3383                            .justify_between()
3384                            .opacity(0.8)
3385                            .hover(|style| style.opacity(1.))
3386                            .child(
3387                                h_flex()
3388                                    .gap_1p5()
3389                                    .child(
3390                                        Icon::new(IconName::LightBulb)
3391                                            .size(IconSize::XSmall)
3392                                            .color(Color::Muted),
3393                                    )
3394                                    .child(Label::new("Thought Process").size(LabelSize::Small)),
3395                            )
3396                            .child(
3397                                div().visible_on_hover("disclosure-header").child(
3398                                    Disclosure::new("thinking-disclosure", is_open)
3399                                        .opened_icon(IconName::ChevronUp)
3400                                        .closed_icon(IconName::ChevronDown)
3401                                        .on_click(cx.listener({
3402                                            move |this, _event, _window, _cx| {
3403                                                let is_open = this
3404                                                    .expanded_thinking_segments
3405                                                    .entry((message_id, ix))
3406                                                    .or_insert(false);
3407
3408                                                *is_open = !*is_open;
3409                                            }
3410                                        })),
3411                                ),
3412                            ),
3413                    )
3414                    .child(
3415                        div()
3416                            .id(("thinking-content", ix))
3417                            .relative()
3418                            .mt_1p5()
3419                            .ml_1p5()
3420                            .pl_2p5()
3421                            .border_l_1()
3422                            .border_color(cx.theme().colors().border_variant)
3423                            .text_ui_sm(cx)
3424                            .when(is_open, |this| {
3425                                this.child(
3426                                    MarkdownElement::new(
3427                                        markdown.clone(),
3428                                        default_markdown_style(window, cx),
3429                                    )
3430                                    .on_url_click({
3431                                        let workspace = self.workspace.clone();
3432                                        move |text, window, cx| {
3433                                            open_markdown_link(text, workspace.clone(), window, cx);
3434                                        }
3435                                    }),
3436                                )
3437                            }),
3438                    )
3439            }
3440        })
3441    }
3442
3443    fn render_tool_use(
3444        &self,
3445        tool_use: ToolUse,
3446        window: &mut Window,
3447        workspace: WeakEntity<Workspace>,
3448        cx: &mut Context<Self>,
3449    ) -> impl IntoElement + use<> {
3450        if let Some(card) = self.thread.read(cx).card_for_tool(&tool_use.id) {
3451            return card.render(&tool_use.status, window, workspace, cx);
3452        }
3453
3454        let is_open = self
3455            .expanded_tool_uses
3456            .get(&tool_use.id)
3457            .copied()
3458            .unwrap_or_default();
3459
3460        let is_status_finished = matches!(&tool_use.status, ToolUseStatus::Finished(_));
3461
3462        let fs = self
3463            .workspace
3464            .upgrade()
3465            .map(|workspace| workspace.read(cx).app_state().fs.clone());
3466        let needs_confirmation = matches!(&tool_use.status, ToolUseStatus::NeedsConfirmation);
3467        let needs_confirmation_tools = tool_use.needs_confirmation;
3468
3469        let status_icons = div().child(match &tool_use.status {
3470            ToolUseStatus::NeedsConfirmation => {
3471                let icon = Icon::new(IconName::Warning)
3472                    .color(Color::Warning)
3473                    .size(IconSize::Small);
3474                icon.into_any_element()
3475            }
3476            ToolUseStatus::Pending
3477            | ToolUseStatus::InputStillStreaming
3478            | ToolUseStatus::Running => {
3479                let icon = Icon::new(IconName::ArrowCircle)
3480                    .color(Color::Accent)
3481                    .size(IconSize::Small);
3482                icon.with_animation(
3483                    "arrow-circle",
3484                    Animation::new(Duration::from_secs(2)).repeat(),
3485                    |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
3486                )
3487                .into_any_element()
3488            }
3489            ToolUseStatus::Finished(_) => div().w_0().into_any_element(),
3490            ToolUseStatus::Error(_) => {
3491                let icon = Icon::new(IconName::Close)
3492                    .color(Color::Error)
3493                    .size(IconSize::Small);
3494                icon.into_any_element()
3495            }
3496        });
3497
3498        let rendered_tool_use = self.rendered_tool_uses.get(&tool_use.id).cloned();
3499        let results_content_container = || v_flex().p_2().gap_0p5();
3500
3501        let results_content = v_flex()
3502            .gap_1()
3503            .child(
3504                results_content_container()
3505                    .child(
3506                        Label::new("Input")
3507                            .size(LabelSize::XSmall)
3508                            .color(Color::Muted)
3509                            .buffer_font(cx),
3510                    )
3511                    .child(
3512                        div()
3513                            .w_full()
3514                            .text_ui_sm(cx)
3515                            .children(rendered_tool_use.as_ref().map(|rendered| {
3516                                MarkdownElement::new(
3517                                    rendered.input.clone(),
3518                                    tool_use_markdown_style(window, cx),
3519                                )
3520                                .code_block_renderer(markdown::CodeBlockRenderer::Default {
3521                                    copy_button: false,
3522                                    copy_button_on_hover: false,
3523                                    border: false,
3524                                })
3525                                .on_url_click({
3526                                    let workspace = self.workspace.clone();
3527                                    move |text, window, cx| {
3528                                        open_markdown_link(text, workspace.clone(), window, cx);
3529                                    }
3530                                })
3531                            })),
3532                    ),
3533            )
3534            .map(|container| match tool_use.status {
3535                ToolUseStatus::Finished(_) => container.child(
3536                    results_content_container()
3537                        .border_t_1()
3538                        .border_color(self.tool_card_border_color(cx))
3539                        .child(
3540                            Label::new("Result")
3541                                .size(LabelSize::XSmall)
3542                                .color(Color::Muted)
3543                                .buffer_font(cx),
3544                        )
3545                        .child(div().w_full().text_ui_sm(cx).children(
3546                            rendered_tool_use.as_ref().map(|rendered| {
3547                                MarkdownElement::new(
3548                                    rendered.output.clone(),
3549                                    tool_use_markdown_style(window, cx),
3550                                )
3551                                .code_block_renderer(markdown::CodeBlockRenderer::Default {
3552                                    copy_button: false,
3553                                    copy_button_on_hover: false,
3554                                    border: false,
3555                                })
3556                                .on_url_click({
3557                                    let workspace = self.workspace.clone();
3558                                    move |text, window, cx| {
3559                                        open_markdown_link(text, workspace.clone(), window, cx);
3560                                    }
3561                                })
3562                                .into_any_element()
3563                            }),
3564                        )),
3565                ),
3566                ToolUseStatus::InputStillStreaming | ToolUseStatus::Running => container.child(
3567                    results_content_container()
3568                        .border_t_1()
3569                        .border_color(self.tool_card_border_color(cx))
3570                        .child(
3571                            h_flex()
3572                                .gap_1()
3573                                .child(
3574                                    Icon::new(IconName::ArrowCircle)
3575                                        .size(IconSize::Small)
3576                                        .color(Color::Accent)
3577                                        .with_animation(
3578                                            "arrow-circle",
3579                                            Animation::new(Duration::from_secs(2)).repeat(),
3580                                            |icon, delta| {
3581                                                icon.transform(Transformation::rotate(percentage(
3582                                                    delta,
3583                                                )))
3584                                            },
3585                                        ),
3586                                )
3587                                .child(
3588                                    Label::new("Running…")
3589                                        .size(LabelSize::XSmall)
3590                                        .color(Color::Muted)
3591                                        .buffer_font(cx),
3592                                ),
3593                        ),
3594                ),
3595                ToolUseStatus::Error(_) => container.child(
3596                    results_content_container()
3597                        .border_t_1()
3598                        .border_color(self.tool_card_border_color(cx))
3599                        .child(
3600                            Label::new("Error")
3601                                .size(LabelSize::XSmall)
3602                                .color(Color::Muted)
3603                                .buffer_font(cx),
3604                        )
3605                        .child(
3606                            div()
3607                                .text_ui_sm(cx)
3608                                .children(rendered_tool_use.as_ref().map(|rendered| {
3609                                    MarkdownElement::new(
3610                                        rendered.output.clone(),
3611                                        tool_use_markdown_style(window, cx),
3612                                    )
3613                                    .on_url_click({
3614                                        let workspace = self.workspace.clone();
3615                                        move |text, window, cx| {
3616                                            open_markdown_link(text, workspace.clone(), window, cx);
3617                                        }
3618                                    })
3619                                    .into_any_element()
3620                                })),
3621                        ),
3622                ),
3623                ToolUseStatus::Pending => container,
3624                ToolUseStatus::NeedsConfirmation => container.child(
3625                    results_content_container()
3626                        .border_t_1()
3627                        .border_color(self.tool_card_border_color(cx))
3628                        .child(
3629                            Label::new("Asking Permission")
3630                                .size(LabelSize::Small)
3631                                .color(Color::Muted)
3632                                .buffer_font(cx),
3633                        ),
3634                ),
3635            });
3636
3637        let gradient_overlay = |color: Hsla| {
3638            div()
3639                .h_full()
3640                .absolute()
3641                .w_12()
3642                .bottom_0()
3643                .map(|element| {
3644                    if is_status_finished {
3645                        element.right_6()
3646                    } else {
3647                        element.right(px(44.))
3648                    }
3649                })
3650                .bg(linear_gradient(
3651                    90.,
3652                    linear_color_stop(color, 1.),
3653                    linear_color_stop(color.opacity(0.2), 0.),
3654                ))
3655        };
3656
3657        v_flex().gap_1().mb_2().map(|element| {
3658            if !needs_confirmation_tools {
3659                element.child(
3660                    v_flex()
3661                        .child(
3662                            h_flex()
3663                                .group("disclosure-header")
3664                                .relative()
3665                                .gap_1p5()
3666                                .justify_between()
3667                                .opacity(0.8)
3668                                .hover(|style| style.opacity(1.))
3669                                .when(!is_status_finished, |this| this.pr_2())
3670                                .child(
3671                                    h_flex()
3672                                        .id("tool-label-container")
3673                                        .gap_1p5()
3674                                        .max_w_full()
3675                                        .overflow_x_scroll()
3676                                        .child(
3677                                            Icon::new(tool_use.icon)
3678                                                .size(IconSize::Small)
3679                                                .color(Color::Muted),
3680                                        )
3681                                        .child(
3682                                            h_flex().pr_8().text_size(rems(0.8125)).children(
3683                                                rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| {
3684                                                    open_markdown_link(text, workspace.clone(), window, cx);
3685                                                }}))
3686                                            ),
3687                                        ),
3688                                )
3689                                .child(
3690                                    h_flex()
3691                                        .gap_1()
3692                                        .child(
3693                                            div().visible_on_hover("disclosure-header").child(
3694                                                Disclosure::new("tool-use-disclosure", is_open)
3695                                                    .opened_icon(IconName::ChevronUp)
3696                                                    .closed_icon(IconName::ChevronDown)
3697                                                    .on_click(cx.listener({
3698                                                        let tool_use_id = tool_use.id.clone();
3699                                                        move |this, _event, _window, _cx| {
3700                                                            let is_open = this
3701                                                                .expanded_tool_uses
3702                                                                .entry(tool_use_id.clone())
3703                                                                .or_insert(false);
3704
3705                                                            *is_open = !*is_open;
3706                                                        }
3707                                                    })),
3708                                            ),
3709                                        )
3710                                        .child(status_icons),
3711                                )
3712                                .child(gradient_overlay(cx.theme().colors().panel_background)),
3713                        )
3714                        .map(|parent| {
3715                            if !is_open {
3716                                return parent;
3717                            }
3718
3719                            parent.child(
3720                                v_flex()
3721                                    .mt_1()
3722                                    .border_1()
3723                                    .border_color(self.tool_card_border_color(cx))
3724                                    .bg(cx.theme().colors().editor_background)
3725                                    .rounded_lg()
3726                                    .child(results_content),
3727                            )
3728                        }),
3729                )
3730            } else {
3731                v_flex()
3732                    .mb_2()
3733                    .rounded_lg()
3734                    .border_1()
3735                    .border_color(self.tool_card_border_color(cx))
3736                    .overflow_hidden()
3737                    .child(
3738                        h_flex()
3739                            .group("disclosure-header")
3740                            .relative()
3741                            .justify_between()
3742                            .py_1()
3743                            .map(|element| {
3744                                if is_status_finished {
3745                                    element.pl_2().pr_0p5()
3746                                } else {
3747                                    element.px_2()
3748                                }
3749                            })
3750                            .bg(self.tool_card_header_bg(cx))
3751                            .map(|element| {
3752                                if is_open {
3753                                    element.border_b_1().rounded_t_md()
3754                                } else if needs_confirmation {
3755                                    element.rounded_t_md()
3756                                } else {
3757                                    element.rounded_md()
3758                                }
3759                            })
3760                            .border_color(self.tool_card_border_color(cx))
3761                            .child(
3762                                h_flex()
3763                                    .id("tool-label-container")
3764                                    .gap_1p5()
3765                                    .max_w_full()
3766                                    .overflow_x_scroll()
3767                                    .child(
3768                                        Icon::new(tool_use.icon)
3769                                            .size(IconSize::XSmall)
3770                                            .color(Color::Muted),
3771                                    )
3772                                    .child(
3773                                        h_flex().pr_8().text_ui_sm(cx).children(
3774                                            rendered_tool_use.map(|rendered| MarkdownElement::new(rendered.label, tool_use_markdown_style(window, cx)).on_url_click({let workspace = self.workspace.clone(); move |text, window, cx| {
3775                                                open_markdown_link(text, workspace.clone(), window, cx);
3776                                            }}))
3777                                        ),
3778                                    ),
3779                            )
3780                            .child(
3781                                h_flex()
3782                                    .gap_1()
3783                                    .child(
3784                                        div().visible_on_hover("disclosure-header").child(
3785                                            Disclosure::new("tool-use-disclosure", is_open)
3786                                                .opened_icon(IconName::ChevronUp)
3787                                                .closed_icon(IconName::ChevronDown)
3788                                                .on_click(cx.listener({
3789                                                    let tool_use_id = tool_use.id.clone();
3790                                                    move |this, _event, _window, _cx| {
3791                                                        let is_open = this
3792                                                            .expanded_tool_uses
3793                                                            .entry(tool_use_id.clone())
3794                                                            .or_insert(false);
3795
3796                                                        *is_open = !*is_open;
3797                                                    }
3798                                                })),
3799                                        ),
3800                                    )
3801                                    .child(status_icons),
3802                            )
3803                            .child(gradient_overlay(self.tool_card_header_bg(cx))),
3804                    )
3805                    .map(|parent| {
3806                        if !is_open {
3807                            return parent;
3808                        }
3809
3810                        parent.child(
3811                            v_flex()
3812                                .bg(cx.theme().colors().editor_background)
3813                                .map(|element| {
3814                                    if  needs_confirmation {
3815                                        element.rounded_none()
3816                                    } else {
3817                                        element.rounded_b_lg()
3818                                    }
3819                                })
3820                                .child(results_content),
3821                        )
3822                    })
3823                    .when(needs_confirmation, |this| {
3824                        this.child(
3825                            h_flex()
3826                                .py_1()
3827                                .pl_2()
3828                                .pr_1()
3829                                .gap_1()
3830                                .justify_between()
3831                                .flex_wrap()
3832                                .bg(cx.theme().colors().editor_background)
3833                                .border_t_1()
3834                                .border_color(self.tool_card_border_color(cx))
3835                                .rounded_b_lg()
3836                                .child(
3837                                    div()
3838                                        .min_w(rems_from_px(145.))
3839                                        .child(LoadingLabel::new("Waiting for Confirmation").size(LabelSize::Small)
3840                                    )
3841                                )
3842                                .child(
3843                                    h_flex()
3844                                        .gap_0p5()
3845                                        .child({
3846                                            let tool_id = tool_use.id.clone();
3847                                            Button::new(
3848                                                "always-allow-tool-action",
3849                                                "Always Allow",
3850                                            )
3851                                            .label_size(LabelSize::Small)
3852                                            .icon(IconName::CheckDouble)
3853                                            .icon_position(IconPosition::Start)
3854                                            .icon_size(IconSize::Small)
3855                                            .icon_color(Color::Success)
3856                                            .tooltip(move |window, cx|  {
3857                                                Tooltip::with_meta(
3858                                                    "Never ask for permission",
3859                                                    None,
3860                                                    "Restore the original behavior in your Agent Panel settings",
3861                                                    window,
3862                                                    cx,
3863                                                )
3864                                            })
3865                                            .on_click(cx.listener(
3866                                                move |this, event, window, cx| {
3867                                                    if let Some(fs) = fs.clone() {
3868                                                        update_settings_file::<AgentSettings>(
3869                                                            fs.clone(),
3870                                                            cx,
3871                                                            |settings, _| {
3872                                                                settings.set_always_allow_tool_actions(true);
3873                                                            },
3874                                                        );
3875                                                    }
3876                                                    this.handle_allow_tool(
3877                                                        tool_id.clone(),
3878                                                        event,
3879                                                        window,
3880                                                        cx,
3881                                                    )
3882                                                },
3883                                            ))
3884                                        })
3885                                        .child({
3886                                            let tool_id = tool_use.id.clone();
3887                                            Button::new("allow-tool-action", "Allow")
3888                                                .label_size(LabelSize::Small)
3889                                                .icon(IconName::Check)
3890                                                .icon_position(IconPosition::Start)
3891                                                .icon_size(IconSize::Small)
3892                                                .icon_color(Color::Success)
3893                                                .on_click(cx.listener(
3894                                                    move |this, event, window, cx| {
3895                                                        this.handle_allow_tool(
3896                                                            tool_id.clone(),
3897                                                            event,
3898                                                            window,
3899                                                            cx,
3900                                                        )
3901                                                    },
3902                                                ))
3903                                        })
3904                                        .child({
3905                                            let tool_id = tool_use.id.clone();
3906                                            let tool_name: Arc<str> = tool_use.name.into();
3907                                            Button::new("deny-tool", "Deny")
3908                                                .label_size(LabelSize::Small)
3909                                                .icon(IconName::Close)
3910                                                .icon_position(IconPosition::Start)
3911                                                .icon_size(IconSize::Small)
3912                                                .icon_color(Color::Error)
3913                                                .on_click(cx.listener(
3914                                                    move |this, event, window, cx| {
3915                                                        this.handle_deny_tool(
3916                                                            tool_id.clone(),
3917                                                            tool_name.clone(),
3918                                                            event,
3919                                                            window,
3920                                                            cx,
3921                                                        )
3922                                                    },
3923                                                ))
3924                                        }),
3925                                ),
3926                        )
3927                    })
3928            }
3929        }).into_any_element()
3930    }
3931
3932    fn render_rules_item(&self, cx: &Context<Self>) -> AnyElement {
3933        let project_context = self.thread.read(cx).project_context();
3934        let project_context = project_context.borrow();
3935        let Some(project_context) = project_context.as_ref() else {
3936            return div().into_any();
3937        };
3938
3939        let user_rules_text = if project_context.user_rules.is_empty() {
3940            None
3941        } else if project_context.user_rules.len() == 1 {
3942            let user_rules = &project_context.user_rules[0];
3943
3944            match user_rules.title.as_ref() {
3945                Some(title) => Some(format!("Using \"{title}\" user rule")),
3946                None => Some("Using user rule".into()),
3947            }
3948        } else {
3949            Some(format!(
3950                "Using {} user rules",
3951                project_context.user_rules.len()
3952            ))
3953        };
3954
3955        let first_user_rules_id = project_context
3956            .user_rules
3957            .first()
3958            .map(|user_rules| user_rules.uuid.0);
3959
3960        let rules_files = project_context
3961            .worktrees
3962            .iter()
3963            .filter_map(|worktree| worktree.rules_file.as_ref())
3964            .collect::<Vec<_>>();
3965
3966        let rules_file_text = match rules_files.as_slice() {
3967            &[] => None,
3968            &[rules_file] => Some(format!(
3969                "Using project {:?} file",
3970                rules_file.path_in_worktree
3971            )),
3972            rules_files => Some(format!("Using {} project rules files", rules_files.len())),
3973        };
3974
3975        if user_rules_text.is_none() && rules_file_text.is_none() {
3976            return div().into_any();
3977        }
3978
3979        v_flex()
3980            .pt_2()
3981            .px_2p5()
3982            .gap_1()
3983            .when_some(user_rules_text, |parent, user_rules_text| {
3984                parent.child(
3985                    h_flex()
3986                        .w_full()
3987                        .child(
3988                            Icon::new(RULES_ICON)
3989                                .size(IconSize::XSmall)
3990                                .color(Color::Disabled),
3991                        )
3992                        .child(
3993                            Label::new(user_rules_text)
3994                                .size(LabelSize::XSmall)
3995                                .color(Color::Muted)
3996                                .truncate()
3997                                .buffer_font(cx)
3998                                .ml_1p5()
3999                                .mr_0p5(),
4000                        )
4001                        .child(
4002                            IconButton::new("open-prompt-library", IconName::ArrowUpRightAlt)
4003                                .shape(ui::IconButtonShape::Square)
4004                                .icon_size(IconSize::XSmall)
4005                                .icon_color(Color::Ignored)
4006                                // TODO: Figure out a way to pass focus handle here so we can display the `OpenRulesLibrary`  keybinding
4007                                .tooltip(Tooltip::text("View User Rules"))
4008                                .on_click(move |_event, window, cx| {
4009                                    window.dispatch_action(
4010                                        Box::new(OpenRulesLibrary {
4011                                            prompt_to_select: first_user_rules_id,
4012                                        }),
4013                                        cx,
4014                                    )
4015                                }),
4016                        ),
4017                )
4018            })
4019            .when_some(rules_file_text, |parent, rules_file_text| {
4020                parent.child(
4021                    h_flex()
4022                        .w_full()
4023                        .child(
4024                            Icon::new(IconName::File)
4025                                .size(IconSize::XSmall)
4026                                .color(Color::Disabled),
4027                        )
4028                        .child(
4029                            Label::new(rules_file_text)
4030                                .size(LabelSize::XSmall)
4031                                .color(Color::Muted)
4032                                .buffer_font(cx)
4033                                .ml_1p5()
4034                                .mr_0p5(),
4035                        )
4036                        .child(
4037                            IconButton::new("open-rule", IconName::ArrowUpRightAlt)
4038                                .shape(ui::IconButtonShape::Square)
4039                                .icon_size(IconSize::XSmall)
4040                                .icon_color(Color::Ignored)
4041                                .on_click(cx.listener(Self::handle_open_rules))
4042                                .tooltip(Tooltip::text("View Rules")),
4043                        ),
4044                )
4045            })
4046            .into_any()
4047    }
4048
4049    fn handle_allow_tool(
4050        &mut self,
4051        tool_use_id: LanguageModelToolUseId,
4052        _: &ClickEvent,
4053        window: &mut Window,
4054        cx: &mut Context<Self>,
4055    ) {
4056        if let Some(PendingToolUseStatus::NeedsConfirmation(c)) = self
4057            .thread
4058            .read(cx)
4059            .pending_tool(&tool_use_id)
4060            .map(|tool_use| tool_use.status.clone())
4061        {
4062            self.thread.update(cx, |thread, cx| {
4063                if let Some(configured) = thread.get_or_init_configured_model(cx) {
4064                    thread.run_tool(
4065                        c.tool_use_id.clone(),
4066                        c.ui_text.clone(),
4067                        c.input.clone(),
4068                        c.request.clone(),
4069                        c.tool.clone(),
4070                        configured.model,
4071                        Some(window.window_handle()),
4072                        cx,
4073                    );
4074                }
4075            });
4076        }
4077    }
4078
4079    fn handle_deny_tool(
4080        &mut self,
4081        tool_use_id: LanguageModelToolUseId,
4082        tool_name: Arc<str>,
4083        _: &ClickEvent,
4084        window: &mut Window,
4085        cx: &mut Context<Self>,
4086    ) {
4087        let window_handle = window.window_handle();
4088        self.thread.update(cx, |thread, cx| {
4089            thread.deny_tool_use(tool_use_id, tool_name, Some(window_handle), cx);
4090        });
4091    }
4092
4093    fn handle_open_rules(&mut self, _: &ClickEvent, window: &mut Window, cx: &mut Context<Self>) {
4094        let project_context = self.thread.read(cx).project_context();
4095        let project_context = project_context.borrow();
4096        let Some(project_context) = project_context.as_ref() else {
4097            return;
4098        };
4099
4100        let project_entry_ids = project_context
4101            .worktrees
4102            .iter()
4103            .flat_map(|worktree| worktree.rules_file.as_ref())
4104            .map(|rules_file| ProjectEntryId::from_usize(rules_file.project_entry_id))
4105            .collect::<Vec<_>>();
4106
4107        self.workspace
4108            .update(cx, move |workspace, cx| {
4109                // TODO: Open a multibuffer instead? In some cases this doesn't make the set of rules
4110                // files clear. For example, if rules file 1 is already open but rules file 2 is not,
4111                // this would open and focus rules file 2 in a tab that is not next to rules file 1.
4112                let project = workspace.project().read(cx);
4113                let project_paths = project_entry_ids
4114                    .into_iter()
4115                    .flat_map(|entry_id| project.path_for_entry(entry_id, cx))
4116                    .collect::<Vec<_>>();
4117                for project_path in project_paths {
4118                    workspace
4119                        .open_path(project_path, None, true, window, cx)
4120                        .detach_and_log_err(cx);
4121                }
4122            })
4123            .ok();
4124    }
4125
4126    fn dismiss_notifications(&mut self, cx: &mut Context<ActiveThread>) {
4127        for window in self.notifications.drain(..) {
4128            window
4129                .update(cx, |_, window, _| {
4130                    window.remove_window();
4131                })
4132                .ok();
4133
4134            self.notification_subscriptions.remove(&window);
4135        }
4136    }
4137
4138    fn render_vertical_scrollbar(&self, cx: &mut Context<Self>) -> Option<Stateful<Div>> {
4139        if let Some((_, state)) = &self.editing_message {
4140            if state.is_agent_message {
4141                return None;
4142            }
4143        }
4144
4145        if !self.show_scrollbar && !self.scrollbar_state.is_dragging() {
4146            return None;
4147        }
4148
4149        Some(
4150            div()
4151                .occlude()
4152                .id("active-thread-scrollbar")
4153                .on_mouse_move(cx.listener(|_, _, _, cx| {
4154                    cx.notify();
4155                    cx.stop_propagation()
4156                }))
4157                .on_hover(|_, _, cx| {
4158                    cx.stop_propagation();
4159                })
4160                .on_any_mouse_down(|_, _, cx| {
4161                    cx.stop_propagation();
4162                })
4163                .on_mouse_up(
4164                    MouseButton::Left,
4165                    cx.listener(|_, _, _, cx| {
4166                        cx.stop_propagation();
4167                    }),
4168                )
4169                .on_scroll_wheel(cx.listener(|_, _, _, cx| {
4170                    cx.notify();
4171                }))
4172                .h_full()
4173                .absolute()
4174                .right_1()
4175                .top_1()
4176                .bottom_0()
4177                .w(px(12.))
4178                .cursor_default()
4179                .children(Scrollbar::vertical(self.scrollbar_state.clone())),
4180        )
4181    }
4182
4183    fn hide_scrollbar_later(&mut self, cx: &mut Context<Self>) {
4184        const SCROLLBAR_SHOW_INTERVAL: Duration = Duration::from_secs(1);
4185        self.hide_scrollbar_task = Some(cx.spawn(async move |thread, cx| {
4186            cx.background_executor()
4187                .timer(SCROLLBAR_SHOW_INTERVAL)
4188                .await;
4189            thread
4190                .update(cx, |thread, cx| {
4191                    if !thread.scrollbar_state.is_dragging() {
4192                        thread.show_scrollbar = false;
4193                        cx.notify();
4194                    }
4195                })
4196                .log_err();
4197        }))
4198    }
4199
4200    pub fn is_codeblock_expanded(&self, message_id: MessageId, ix: usize) -> bool {
4201        self.expanded_code_blocks
4202            .get(&(message_id, ix))
4203            .copied()
4204            .unwrap_or(true)
4205    }
4206
4207    pub fn toggle_codeblock_expanded(&mut self, message_id: MessageId, ix: usize) {
4208        let is_expanded = self
4209            .expanded_code_blocks
4210            .entry((message_id, ix))
4211            .or_insert(true);
4212        *is_expanded = !*is_expanded;
4213    }
4214
4215    pub fn scroll_to_top(&mut self, cx: &mut Context<Self>) {
4216        self.list_state.scroll_to(ListOffset::default());
4217        cx.notify();
4218    }
4219
4220    pub fn scroll_to_bottom(&mut self, cx: &mut Context<Self>) {
4221        self.list_state.reset(self.messages.len());
4222        cx.notify();
4223    }
4224}
4225
4226pub enum ActiveThreadEvent {
4227    EditingMessageTokenCountChanged,
4228}
4229
4230impl EventEmitter<ActiveThreadEvent> for ActiveThread {}
4231
4232impl Render for ActiveThread {
4233    fn render(&mut self, _window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
4234        v_flex()
4235            .size_full()
4236            .relative()
4237            .bg(cx.theme().colors().panel_background)
4238            .on_mouse_move(cx.listener(|this, _, _, cx| {
4239                this.show_scrollbar = true;
4240                this.hide_scrollbar_later(cx);
4241                cx.notify();
4242            }))
4243            .on_scroll_wheel(cx.listener(|this, _, _, cx| {
4244                this.show_scrollbar = true;
4245                this.hide_scrollbar_later(cx);
4246                cx.notify();
4247            }))
4248            .on_mouse_up(
4249                MouseButton::Left,
4250                cx.listener(|this, _, _, cx| {
4251                    this.hide_scrollbar_later(cx);
4252                }),
4253            )
4254            .child(list(self.list_state.clone()).flex_grow())
4255            .when_some(self.render_vertical_scrollbar(cx), |this, scrollbar| {
4256                this.child(scrollbar)
4257            })
4258    }
4259}
4260
4261pub(crate) fn open_active_thread_as_markdown(
4262    thread: Entity<Thread>,
4263    workspace: Entity<Workspace>,
4264    window: &mut Window,
4265    cx: &mut App,
4266) -> Task<anyhow::Result<()>> {
4267    let markdown_language_task = workspace
4268        .read(cx)
4269        .app_state()
4270        .languages
4271        .language_for_name("Markdown");
4272
4273    window.spawn(cx, async move |cx| {
4274        let markdown_language = markdown_language_task.await?;
4275
4276        workspace.update_in(cx, |workspace, window, cx| {
4277            let thread = thread.read(cx);
4278            let markdown = thread.to_markdown(cx)?;
4279            let thread_summary = thread.summary().or_default().to_string();
4280
4281            let project = workspace.project().clone();
4282
4283            if !project.read(cx).is_local() {
4284                anyhow::bail!("failed to open active thread as markdown in remote project");
4285            }
4286
4287            let buffer = project.update(cx, |project, cx| {
4288                project.create_local_buffer(&markdown, Some(markdown_language), cx)
4289            });
4290            let buffer =
4291                cx.new(|cx| MultiBuffer::singleton(buffer, cx).with_title(thread_summary.clone()));
4292
4293            workspace.add_item_to_active_pane(
4294                Box::new(cx.new(|cx| {
4295                    let mut editor =
4296                        Editor::for_multibuffer(buffer, Some(project.clone()), window, cx);
4297                    editor.set_breadcrumb_header(thread_summary);
4298                    editor
4299                })),
4300                None,
4301                true,
4302                window,
4303                cx,
4304            );
4305
4306            anyhow::Ok(())
4307        })??;
4308        anyhow::Ok(())
4309    })
4310}
4311
4312pub(crate) fn open_context(
4313    context: &AgentContextHandle,
4314    workspace: Entity<Workspace>,
4315    window: &mut Window,
4316    cx: &mut App,
4317) {
4318    match context {
4319        AgentContextHandle::File(file_context) => {
4320            if let Some(project_path) = file_context.project_path(cx) {
4321                workspace.update(cx, |workspace, cx| {
4322                    workspace
4323                        .open_path(project_path, None, true, window, cx)
4324                        .detach_and_log_err(cx);
4325                });
4326            }
4327        }
4328
4329        AgentContextHandle::Directory(directory_context) => {
4330            let entry_id = directory_context.entry_id;
4331            workspace.update(cx, |workspace, cx| {
4332                workspace.project().update(cx, |_project, cx| {
4333                    cx.emit(project::Event::RevealInProjectPanel(entry_id));
4334                })
4335            })
4336        }
4337
4338        AgentContextHandle::Symbol(symbol_context) => {
4339            let buffer = symbol_context.buffer.read(cx);
4340            if let Some(project_path) = buffer.project_path(cx) {
4341                let snapshot = buffer.snapshot();
4342                let target_position = symbol_context.range.start.to_point(&snapshot);
4343                open_editor_at_position(project_path, target_position, &workspace, window, cx)
4344                    .detach();
4345            }
4346        }
4347
4348        AgentContextHandle::Selection(selection_context) => {
4349            let buffer = selection_context.buffer.read(cx);
4350            if let Some(project_path) = buffer.project_path(cx) {
4351                let snapshot = buffer.snapshot();
4352                let target_position = selection_context.range.start.to_point(&snapshot);
4353
4354                open_editor_at_position(project_path, target_position, &workspace, window, cx)
4355                    .detach();
4356            }
4357        }
4358
4359        AgentContextHandle::FetchedUrl(fetched_url_context) => {
4360            cx.open_url(&fetched_url_context.url);
4361        }
4362
4363        AgentContextHandle::Thread(thread_context) => workspace.update(cx, |workspace, cx| {
4364            if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
4365                let thread = thread_context.thread.clone();
4366                window.defer(cx, move |window, cx| {
4367                    panel.update(cx, |panel, cx| {
4368                        panel.open_thread(thread, window, cx);
4369                    });
4370                });
4371            }
4372        }),
4373
4374        AgentContextHandle::TextThread(text_thread_context) => {
4375            workspace.update(cx, |workspace, cx| {
4376                if let Some(panel) = workspace.panel::<AgentPanel>(cx) {
4377                    let context = text_thread_context.context.clone();
4378                    window.defer(cx, move |window, cx| {
4379                        panel.update(cx, |panel, cx| {
4380                            panel.open_prompt_editor(context, window, cx)
4381                        });
4382                    });
4383                }
4384            })
4385        }
4386
4387        AgentContextHandle::Rules(rules_context) => window.dispatch_action(
4388            Box::new(OpenRulesLibrary {
4389                prompt_to_select: Some(rules_context.prompt_id.0),
4390            }),
4391            cx,
4392        ),
4393
4394        AgentContextHandle::Image(_) => {}
4395    }
4396}
4397
4398pub(crate) fn attach_pasted_images_as_context(
4399    context_store: &Entity<ContextStore>,
4400    cx: &mut App,
4401) -> bool {
4402    let images = cx
4403        .read_from_clipboard()
4404        .map(|item| {
4405            item.into_entries()
4406                .filter_map(|entry| {
4407                    if let ClipboardEntry::Image(image) = entry {
4408                        Some(image)
4409                    } else {
4410                        None
4411                    }
4412                })
4413                .collect::<Vec<_>>()
4414        })
4415        .unwrap_or_default();
4416
4417    if images.is_empty() {
4418        return false;
4419    }
4420    cx.stop_propagation();
4421
4422    context_store.update(cx, |store, cx| {
4423        for image in images {
4424            store.add_image_instance(Arc::new(image), cx);
4425        }
4426    });
4427    true
4428}
4429
4430fn open_editor_at_position(
4431    project_path: project::ProjectPath,
4432    target_position: Point,
4433    workspace: &Entity<Workspace>,
4434    window: &mut Window,
4435    cx: &mut App,
4436) -> Task<()> {
4437    let open_task = workspace.update(cx, |workspace, cx| {
4438        workspace.open_path(project_path, None, true, window, cx)
4439    });
4440    window.spawn(cx, async move |cx| {
4441        if let Some(active_editor) = open_task
4442            .await
4443            .log_err()
4444            .and_then(|item| item.downcast::<Editor>())
4445        {
4446            active_editor
4447                .downgrade()
4448                .update_in(cx, |editor, window, cx| {
4449                    editor.go_to_singleton_buffer_point(target_position, window, cx);
4450                })
4451                .log_err();
4452        }
4453    })
4454}
4455
4456#[cfg(test)]
4457mod tests {
4458    use super::*;
4459    use agent::{MessageSegment, context::ContextLoadResult, thread_store};
4460    use assistant_tool::{ToolRegistry, ToolWorkingSet};
4461    use editor::EditorSettings;
4462    use fs::FakeFs;
4463    use gpui::{AppContext, TestAppContext, VisualTestContext};
4464    use language_model::{
4465        ConfiguredModel, LanguageModel, LanguageModelRegistry,
4466        fake_provider::{FakeLanguageModel, FakeLanguageModelProvider},
4467    };
4468    use project::Project;
4469    use prompt_store::PromptBuilder;
4470    use serde_json::json;
4471    use settings::SettingsStore;
4472    use util::path;
4473    use workspace::CollaboratorId;
4474
4475    #[gpui::test]
4476    async fn test_agent_is_unfollowed_after_cancelling_completion(cx: &mut TestAppContext) {
4477        init_test_settings(cx);
4478
4479        let project = create_test_project(
4480            cx,
4481            json!({"code.rs": "fn main() {\n    println!(\"Hello, world!\");\n}"}),
4482        )
4483        .await;
4484
4485        let (cx, _active_thread, workspace, thread, model) =
4486            setup_test_environment(cx, project.clone()).await;
4487
4488        // Insert user message without any context (empty context vector)
4489        thread.update(cx, |thread, cx| {
4490            thread.insert_user_message(
4491                "What is the best way to learn Rust?",
4492                ContextLoadResult::default(),
4493                None,
4494                vec![],
4495                cx,
4496            );
4497        });
4498
4499        // Stream response to user message
4500        thread.update(cx, |thread, cx| {
4501            let intent = CompletionIntent::UserPrompt;
4502            let request = thread.to_completion_request(model.clone(), intent, cx);
4503            thread.stream_completion(request, model, intent, cx.active_window(), cx)
4504        });
4505        // Follow the agent
4506        cx.update(|window, cx| {
4507            workspace.update(cx, |workspace, cx| {
4508                workspace.follow(CollaboratorId::Agent, window, cx);
4509            })
4510        });
4511        assert!(cx.read(|cx| workspace.read(cx).is_being_followed(CollaboratorId::Agent)));
4512
4513        // Cancel the current completion
4514        thread.update(cx, |thread, cx| {
4515            thread.cancel_last_completion(cx.active_window(), cx)
4516        });
4517
4518        cx.executor().run_until_parked();
4519
4520        // No longer following the agent
4521        assert!(!cx.read(|cx| workspace.read(cx).is_being_followed(CollaboratorId::Agent)));
4522    }
4523
4524    #[gpui::test]
4525    async fn test_reinserting_creases_for_edited_message(cx: &mut TestAppContext) {
4526        init_test_settings(cx);
4527
4528        let project = create_test_project(cx, json!({})).await;
4529
4530        let (cx, active_thread, _, thread, model) =
4531            setup_test_environment(cx, project.clone()).await;
4532        cx.update(|_, cx| {
4533            LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
4534                registry.set_default_model(
4535                    Some(ConfiguredModel {
4536                        provider: Arc::new(FakeLanguageModelProvider::default()),
4537                        model,
4538                    }),
4539                    cx,
4540                );
4541            });
4542        });
4543
4544        let creases = vec![MessageCrease {
4545            range: 14..22,
4546            icon_path: "icon".into(),
4547            label: "foo.txt".into(),
4548            context: None,
4549        }];
4550
4551        let message = thread.update(cx, |thread, cx| {
4552            let message_id = thread.insert_user_message(
4553                "Tell me about @foo.txt",
4554                ContextLoadResult::default(),
4555                None,
4556                creases,
4557                cx,
4558            );
4559            thread.message(message_id).cloned().unwrap()
4560        });
4561
4562        active_thread.update_in(cx, |active_thread, window, cx| {
4563            if let Some(message_text) = message.segments.first().and_then(MessageSegment::text) {
4564                active_thread.start_editing_user_message(
4565                    message.id,
4566                    message_text,
4567                    message.creases.as_slice(),
4568                    None,
4569                    window,
4570                    cx,
4571                );
4572            }
4573            let editor = active_thread
4574                .editing_message
4575                .as_ref()
4576                .unwrap()
4577                .1
4578                .editor
4579                .clone();
4580            editor.update(cx, |editor, cx| editor.edit([(0..13, "modified")], cx));
4581            active_thread.confirm_editing_message(&Default::default(), window, cx);
4582        });
4583        cx.run_until_parked();
4584
4585        let message = thread.update(cx, |thread, _| thread.message(message.id).cloned().unwrap());
4586        active_thread.update_in(cx, |active_thread, window, cx| {
4587            if let Some(message_text) = message.segments.first().and_then(MessageSegment::text) {
4588                active_thread.start_editing_user_message(
4589                    message.id,
4590                    message_text,
4591                    message.creases.as_slice(),
4592                    None,
4593                    window,
4594                    cx,
4595                );
4596            }
4597            let editor = active_thread
4598                .editing_message
4599                .as_ref()
4600                .unwrap()
4601                .1
4602                .editor
4603                .clone();
4604            let text = editor.update(cx, |editor, cx| editor.text(cx));
4605            assert_eq!(text, "modified @foo.txt");
4606        });
4607    }
4608
4609    #[gpui::test]
4610    async fn test_editing_message_cancels_previous_completion(cx: &mut TestAppContext) {
4611        init_test_settings(cx);
4612
4613        let project = create_test_project(cx, json!({})).await;
4614
4615        let (cx, active_thread, _, thread, model) =
4616            setup_test_environment(cx, project.clone()).await;
4617
4618        cx.update(|_, cx| {
4619            LanguageModelRegistry::global(cx).update(cx, |registry, cx| {
4620                registry.set_default_model(
4621                    Some(ConfiguredModel {
4622                        provider: Arc::new(FakeLanguageModelProvider::default()),
4623                        model: model.clone(),
4624                    }),
4625                    cx,
4626                );
4627            });
4628        });
4629
4630        // Track thread events to verify cancellation
4631        let cancellation_events = Arc::new(std::sync::Mutex::new(Vec::new()));
4632        let new_request_events = Arc::new(std::sync::Mutex::new(Vec::new()));
4633
4634        let _subscription = cx.update(|_, cx| {
4635            let cancellation_events = cancellation_events.clone();
4636            let new_request_events = new_request_events.clone();
4637            cx.subscribe(
4638                &thread,
4639                move |_thread, event: &ThreadEvent, _cx| match event {
4640                    ThreadEvent::CompletionCanceled => {
4641                        cancellation_events.lock().unwrap().push(());
4642                    }
4643                    ThreadEvent::NewRequest => {
4644                        new_request_events.lock().unwrap().push(());
4645                    }
4646                    _ => {}
4647                },
4648            )
4649        });
4650
4651        // Insert a user message and start streaming a response
4652        let message = thread.update(cx, |thread, cx| {
4653            let message_id = thread.insert_user_message(
4654                "Hello, how are you?",
4655                ContextLoadResult::default(),
4656                None,
4657                vec![],
4658                cx,
4659            );
4660            thread.advance_prompt_id();
4661            thread.send_to_model(
4662                model.clone(),
4663                CompletionIntent::UserPrompt,
4664                cx.active_window(),
4665                cx,
4666            );
4667            thread.message(message_id).cloned().unwrap()
4668        });
4669
4670        cx.run_until_parked();
4671
4672        // Verify that a completion is in progress
4673        assert!(cx.read(|cx| thread.read(cx).is_generating()));
4674        assert_eq!(new_request_events.lock().unwrap().len(), 1);
4675
4676        // Edit the message while the completion is still running
4677        active_thread.update_in(cx, |active_thread, window, cx| {
4678            if let Some(message_text) = message.segments.first().and_then(MessageSegment::text) {
4679                active_thread.start_editing_user_message(
4680                    message.id,
4681                    message_text,
4682                    message.creases.as_slice(),
4683                    None,
4684                    window,
4685                    cx,
4686                );
4687            }
4688            let editor = active_thread
4689                .editing_message
4690                .as_ref()
4691                .unwrap()
4692                .1
4693                .editor
4694                .clone();
4695            editor.update(cx, |editor, cx| {
4696                editor.set_text("What is the weather like?", window, cx);
4697            });
4698            active_thread.confirm_editing_message(&Default::default(), window, cx);
4699        });
4700
4701        cx.run_until_parked();
4702
4703        // Verify that the previous completion was cancelled
4704        assert_eq!(cancellation_events.lock().unwrap().len(), 1);
4705
4706        // Verify that a new request was started after cancellation
4707        assert_eq!(new_request_events.lock().unwrap().len(), 2);
4708
4709        // Verify that the edited message contains the new text
4710        let edited_message =
4711            thread.update(cx, |thread, _| thread.message(message.id).cloned().unwrap());
4712        match &edited_message.segments[0] {
4713            MessageSegment::Text(text) => {
4714                assert_eq!(text, "What is the weather like?");
4715            }
4716            _ => panic!("Expected text segment"),
4717        }
4718    }
4719
4720    fn init_test_settings(cx: &mut TestAppContext) {
4721        cx.update(|cx| {
4722            let settings_store = SettingsStore::test(cx);
4723            cx.set_global(settings_store);
4724            language::init(cx);
4725            Project::init_settings(cx);
4726            AgentSettings::register(cx);
4727            prompt_store::init(cx);
4728            thread_store::init(cx);
4729            workspace::init_settings(cx);
4730            language_model::init_settings(cx);
4731            ThemeSettings::register(cx);
4732            EditorSettings::register(cx);
4733            ToolRegistry::default_global(cx);
4734        });
4735    }
4736
4737    // Helper to create a test project with test files
4738    async fn create_test_project(
4739        cx: &mut TestAppContext,
4740        files: serde_json::Value,
4741    ) -> Entity<Project> {
4742        let fs = FakeFs::new(cx.executor());
4743        fs.insert_tree(path!("/test"), files).await;
4744        Project::test(fs, [path!("/test").as_ref()], cx).await
4745    }
4746
4747    async fn setup_test_environment(
4748        cx: &mut TestAppContext,
4749        project: Entity<Project>,
4750    ) -> (
4751        &mut VisualTestContext,
4752        Entity<ActiveThread>,
4753        Entity<Workspace>,
4754        Entity<Thread>,
4755        Arc<dyn LanguageModel>,
4756    ) {
4757        let (workspace, cx) =
4758            cx.add_window_view(|window, cx| Workspace::test_new(project.clone(), window, cx));
4759
4760        let thread_store = cx
4761            .update(|_, cx| {
4762                ThreadStore::load(
4763                    project.clone(),
4764                    cx.new(|_| ToolWorkingSet::default()),
4765                    None,
4766                    Arc::new(PromptBuilder::new(None).unwrap()),
4767                    cx,
4768                )
4769            })
4770            .await
4771            .unwrap();
4772
4773        let text_thread_store = cx
4774            .update(|_, cx| {
4775                TextThreadStore::new(
4776                    project.clone(),
4777                    Arc::new(PromptBuilder::new(None).unwrap()),
4778                    Default::default(),
4779                    cx,
4780                )
4781            })
4782            .await
4783            .unwrap();
4784
4785        let thread = thread_store.update(cx, |store, cx| store.create_thread(cx));
4786        let context_store =
4787            cx.new(|_cx| ContextStore::new(project.downgrade(), Some(thread_store.downgrade())));
4788
4789        let model = FakeLanguageModel::default();
4790        let model: Arc<dyn LanguageModel> = Arc::new(model);
4791
4792        let language_registry = LanguageRegistry::new(cx.executor());
4793        let language_registry = Arc::new(language_registry);
4794
4795        let active_thread = cx.update(|window, cx| {
4796            cx.new(|cx| {
4797                let mut active_thread = ActiveThread::new(
4798                    thread.clone(),
4799                    thread_store.clone(),
4800                    text_thread_store,
4801                    context_store.clone(),
4802                    language_registry.clone(),
4803                    workspace.downgrade(),
4804                    window,
4805                    cx,
4806                );
4807                #[cfg(test)]
4808                {
4809                    active_thread.skip_save_when_testing = true;
4810                }
4811                active_thread
4812            })
4813        });
4814
4815        (cx, active_thread, workspace, thread, model)
4816    }
4817}