text_thread_editor.rs

   1use crate::{
   2    language_model_selector::{LanguageModelSelector, language_model_selector},
   3    ui::BurnModeTooltip,
   4};
   5use agent_settings::CompletionMode;
   6use anyhow::Result;
   7use assistant_slash_command::{SlashCommand, SlashCommandOutputSection, SlashCommandWorkingSet};
   8use assistant_slash_commands::{DefaultSlashCommand, FileSlashCommand, selections_creases};
   9use client::{proto, zed_urls};
  10use collections::{BTreeSet, HashMap, HashSet, hash_map};
  11use editor::{
  12    Anchor, Editor, EditorEvent, MenuEditPredictionsPolicy, MultiBuffer, MultiBufferOffset,
  13    MultiBufferSnapshot, RowExt, ToOffset as _, ToPoint,
  14    actions::{MoveToEndOfLine, Newline, ShowCompletions},
  15    display_map::{
  16        BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata, CustomBlockId, FoldId,
  17        RenderBlock, ToDisplayPoint,
  18    },
  19    scroll::ScrollOffset,
  20};
  21use editor::{FoldPlaceholder, display_map::CreaseId};
  22use fs::Fs;
  23use futures::FutureExt;
  24use gpui::{
  25    Action, Animation, AnimationExt, AnyElement, App, ClipboardEntry, ClipboardItem, Empty, Entity,
  26    EventEmitter, FocusHandle, Focusable, FontWeight, Global, InteractiveElement, IntoElement,
  27    ParentElement, Pixels, Render, RenderImage, SharedString, Size, StatefulInteractiveElement,
  28    Styled, Subscription, Task, WeakEntity, actions, div, img, point, prelude::*,
  29    pulsating_between, size,
  30};
  31use language::{
  32    BufferSnapshot, LspAdapterDelegate, ToOffset,
  33    language_settings::{SoftWrap, all_language_settings},
  34};
  35use language_model::{
  36    ConfigurationError, LanguageModelExt, LanguageModelImage, LanguageModelRegistry, Role,
  37};
  38use multi_buffer::MultiBufferRow;
  39use picker::{Picker, popover_menu::PickerPopoverMenu};
  40use project::{Project, Worktree};
  41use project::{ProjectPath, lsp_store::LocalLspAdapterDelegate};
  42use rope::Point;
  43use serde::{Deserialize, Serialize};
  44use settings::{
  45    LanguageModelProviderSetting, LanguageModelSelection, Settings, SettingsStore,
  46    update_settings_file,
  47};
  48use std::{
  49    any::TypeId,
  50    cmp,
  51    ops::Range,
  52    path::{Path, PathBuf},
  53    rc::Rc,
  54    sync::Arc,
  55    time::Duration,
  56};
  57use text::SelectionGoal;
  58use ui::{
  59    ButtonLike, CommonAnimationExt, Disclosure, ElevationIndex, KeyBinding, PopoverMenuHandle,
  60    TintColor, Tooltip, prelude::*,
  61};
  62use util::{ResultExt, maybe};
  63use workspace::{
  64    CollaboratorId,
  65    searchable::{Direction, SearchableItemHandle},
  66};
  67use workspace::{
  68    Save, Toast, Workspace,
  69    item::{self, FollowableItem, Item},
  70    notifications::NotificationId,
  71    pane,
  72    searchable::{SearchEvent, SearchableItem},
  73};
  74use zed_actions::agent::{AddSelectionToThread, ToggleModelSelector};
  75
  76use crate::{slash_command::SlashCommandCompletionProvider, slash_command_picker};
  77use assistant_text_thread::{
  78    CacheStatus, Content, InvokedSlashCommandId, InvokedSlashCommandStatus, Message, MessageId,
  79    MessageMetadata, MessageStatus, PendingSlashCommandStatus, TextThread, TextThreadEvent,
  80    TextThreadId, ThoughtProcessOutputSection,
  81};
  82
  83actions!(
  84    assistant,
  85    [
  86        /// Sends the current message to the assistant.
  87        Assist,
  88        /// Confirms and executes the entered slash command.
  89        ConfirmCommand,
  90        /// Copies code from the assistant's response to the clipboard.
  91        CopyCode,
  92        /// Cycles between user and assistant message roles.
  93        CycleMessageRole,
  94        /// Inserts the selected text into the active editor.
  95        InsertIntoEditor,
  96        /// Splits the conversation at the current cursor position.
  97        Split,
  98    ]
  99);
 100
 101/// Inserts files that were dragged and dropped into the assistant conversation.
 102#[derive(PartialEq, Clone, Action)]
 103#[action(namespace = assistant, no_json, no_register)]
 104pub enum InsertDraggedFiles {
 105    ProjectPaths(Vec<ProjectPath>),
 106    ExternalFiles(Vec<PathBuf>),
 107}
 108
 109#[derive(Copy, Clone, Debug, PartialEq)]
 110struct ScrollPosition {
 111    offset_before_cursor: gpui::Point<ScrollOffset>,
 112    cursor: Anchor,
 113}
 114
 115type MessageHeader = MessageMetadata;
 116
 117#[derive(Clone)]
 118enum AssistError {
 119    PaymentRequired,
 120    Message(SharedString),
 121}
 122
 123pub enum ThoughtProcessStatus {
 124    Pending,
 125    Completed,
 126}
 127
 128pub trait AgentPanelDelegate {
 129    fn active_text_thread_editor(
 130        &self,
 131        workspace: &mut Workspace,
 132        window: &mut Window,
 133        cx: &mut Context<Workspace>,
 134    ) -> Option<Entity<TextThreadEditor>>;
 135
 136    fn open_local_text_thread(
 137        &self,
 138        workspace: &mut Workspace,
 139        path: Arc<Path>,
 140        window: &mut Window,
 141        cx: &mut Context<Workspace>,
 142    ) -> Task<Result<()>>;
 143
 144    fn open_remote_text_thread(
 145        &self,
 146        workspace: &mut Workspace,
 147        text_thread_id: TextThreadId,
 148        window: &mut Window,
 149        cx: &mut Context<Workspace>,
 150    ) -> Task<Result<Entity<TextThreadEditor>>>;
 151
 152    fn quote_selection(
 153        &self,
 154        workspace: &mut Workspace,
 155        selection_ranges: Vec<Range<Anchor>>,
 156        buffer: Entity<MultiBuffer>,
 157        window: &mut Window,
 158        cx: &mut Context<Workspace>,
 159    );
 160}
 161
 162impl dyn AgentPanelDelegate {
 163    /// Returns the global [`AssistantPanelDelegate`], if it exists.
 164    pub fn try_global(cx: &App) -> Option<Arc<Self>> {
 165        cx.try_global::<GlobalAssistantPanelDelegate>()
 166            .map(|global| global.0.clone())
 167    }
 168
 169    /// Sets the global [`AssistantPanelDelegate`].
 170    pub fn set_global(delegate: Arc<Self>, cx: &mut App) {
 171        cx.set_global(GlobalAssistantPanelDelegate(delegate));
 172    }
 173}
 174
 175struct GlobalAssistantPanelDelegate(Arc<dyn AgentPanelDelegate>);
 176
 177impl Global for GlobalAssistantPanelDelegate {}
 178
 179pub struct TextThreadEditor {
 180    text_thread: Entity<TextThread>,
 181    fs: Arc<dyn Fs>,
 182    slash_commands: Arc<SlashCommandWorkingSet>,
 183    workspace: WeakEntity<Workspace>,
 184    project: Entity<Project>,
 185    lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
 186    editor: Entity<Editor>,
 187    pending_thought_process: Option<(CreaseId, language::Anchor)>,
 188    blocks: HashMap<MessageId, (MessageHeader, CustomBlockId)>,
 189    image_blocks: HashSet<CustomBlockId>,
 190    scroll_position: Option<ScrollPosition>,
 191    remote_id: Option<workspace::ViewId>,
 192    pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
 193    invoked_slash_command_creases: HashMap<InvokedSlashCommandId, CreaseId>,
 194    _subscriptions: Vec<Subscription>,
 195    last_error: Option<AssistError>,
 196    pub(crate) slash_menu_handle:
 197        PopoverMenuHandle<Picker<slash_command_picker::SlashCommandDelegate>>,
 198    // dragged_file_worktrees is used to keep references to worktrees that were added
 199    // when the user drag/dropped an external file onto the context editor. Since
 200    // the worktree is not part of the project panel, it would be dropped as soon as
 201    // the file is opened. In order to keep the worktree alive for the duration of the
 202    // context editor, we keep a reference here.
 203    dragged_file_worktrees: Vec<Entity<Worktree>>,
 204    language_model_selector: Entity<LanguageModelSelector>,
 205    language_model_selector_menu_handle: PopoverMenuHandle<LanguageModelSelector>,
 206}
 207
 208const MAX_TAB_TITLE_LEN: usize = 16;
 209
 210impl TextThreadEditor {
 211    pub fn init(cx: &mut App) {
 212        workspace::FollowableViewRegistry::register::<TextThreadEditor>(cx);
 213
 214        cx.observe_new(
 215            |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
 216                workspace
 217                    .register_action(TextThreadEditor::quote_selection)
 218                    .register_action(TextThreadEditor::insert_selection)
 219                    .register_action(TextThreadEditor::copy_code)
 220                    .register_action(TextThreadEditor::handle_insert_dragged_files);
 221            },
 222        )
 223        .detach();
 224    }
 225
 226    pub fn for_text_thread(
 227        text_thread: Entity<TextThread>,
 228        fs: Arc<dyn Fs>,
 229        workspace: WeakEntity<Workspace>,
 230        project: Entity<Project>,
 231        lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
 232        window: &mut Window,
 233        cx: &mut Context<Self>,
 234    ) -> Self {
 235        let completion_provider = SlashCommandCompletionProvider::new(
 236            text_thread.read(cx).slash_commands().clone(),
 237            Some(cx.entity().downgrade()),
 238            Some(workspace.clone()),
 239        );
 240
 241        let editor = cx.new(|cx| {
 242            let mut editor =
 243                Editor::for_buffer(text_thread.read(cx).buffer().clone(), None, window, cx);
 244            editor.disable_scrollbars_and_minimap(window, cx);
 245            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
 246            editor.set_show_line_numbers(false, cx);
 247            editor.set_show_git_diff_gutter(false, cx);
 248            editor.set_show_code_actions(false, cx);
 249            editor.set_show_runnables(false, cx);
 250            editor.set_show_breakpoints(false, cx);
 251            editor.set_show_wrap_guides(false, cx);
 252            editor.set_show_indent_guides(false, cx);
 253            editor.set_completion_provider(Some(Rc::new(completion_provider)));
 254            editor.set_menu_edit_predictions_policy(MenuEditPredictionsPolicy::Never);
 255            editor.set_collaboration_hub(Box::new(project.clone()));
 256
 257            let show_edit_predictions = all_language_settings(None, cx)
 258                .edit_predictions
 259                .enabled_in_text_threads;
 260
 261            editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
 262
 263            editor
 264        });
 265
 266        let _subscriptions = vec![
 267            cx.observe(&text_thread, |_, _, cx| cx.notify()),
 268            cx.subscribe_in(&text_thread, window, Self::handle_text_thread_event),
 269            cx.subscribe_in(&editor, window, Self::handle_editor_event),
 270            cx.subscribe_in(&editor, window, Self::handle_editor_search_event),
 271            cx.observe_global_in::<SettingsStore>(window, Self::settings_changed),
 272        ];
 273
 274        let slash_command_sections = text_thread
 275            .read(cx)
 276            .slash_command_output_sections()
 277            .to_vec();
 278        let thought_process_sections = text_thread
 279            .read(cx)
 280            .thought_process_output_sections()
 281            .to_vec();
 282        let slash_commands = text_thread.read(cx).slash_commands().clone();
 283        let focus_handle = editor.read(cx).focus_handle(cx);
 284
 285        let mut this = Self {
 286            text_thread,
 287            slash_commands,
 288            editor,
 289            lsp_adapter_delegate,
 290            blocks: Default::default(),
 291            image_blocks: Default::default(),
 292            scroll_position: None,
 293            remote_id: None,
 294            pending_thought_process: None,
 295            fs: fs.clone(),
 296            workspace,
 297            project,
 298            pending_slash_command_creases: HashMap::default(),
 299            invoked_slash_command_creases: HashMap::default(),
 300            _subscriptions,
 301            last_error: None,
 302            slash_menu_handle: Default::default(),
 303            dragged_file_worktrees: Vec::new(),
 304            language_model_selector: cx.new(|cx| {
 305                language_model_selector(
 306                    |cx| LanguageModelRegistry::read_global(cx).default_model(),
 307                    {
 308                        let fs = fs.clone();
 309                        move |model, cx| {
 310                            update_settings_file(fs.clone(), cx, move |settings, _| {
 311                                let provider = model.provider_id().0.to_string();
 312                                let model = model.id().0.to_string();
 313                                settings.agent.get_or_insert_default().set_model(
 314                                    LanguageModelSelection {
 315                                        provider: LanguageModelProviderSetting(provider),
 316                                        model,
 317                                    },
 318                                )
 319                            });
 320                        }
 321                    },
 322                    {
 323                        let fs = fs.clone();
 324                        move |model, should_be_favorite, cx| {
 325                            crate::favorite_models::toggle_in_settings(
 326                                model,
 327                                should_be_favorite,
 328                                fs.clone(),
 329                                cx,
 330                            );
 331                        }
 332                    },
 333                    true, // Use popover styles for picker
 334                    focus_handle,
 335                    window,
 336                    cx,
 337                )
 338            }),
 339            language_model_selector_menu_handle: PopoverMenuHandle::default(),
 340        };
 341        this.update_message_headers(cx);
 342        this.update_image_blocks(cx);
 343        this.insert_slash_command_output_sections(slash_command_sections, false, window, cx);
 344        this.insert_thought_process_output_sections(
 345            thought_process_sections
 346                .into_iter()
 347                .map(|section| (section, ThoughtProcessStatus::Completed)),
 348            window,
 349            cx,
 350        );
 351        this
 352    }
 353
 354    fn settings_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 355        self.editor.update(cx, |editor, cx| {
 356            let show_edit_predictions = all_language_settings(None, cx)
 357                .edit_predictions
 358                .enabled_in_text_threads;
 359
 360            editor.set_show_edit_predictions(Some(show_edit_predictions), window, cx);
 361        });
 362    }
 363
 364    pub fn text_thread(&self) -> &Entity<TextThread> {
 365        &self.text_thread
 366    }
 367
 368    pub fn editor(&self) -> &Entity<Editor> {
 369        &self.editor
 370    }
 371
 372    pub fn insert_default_prompt(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 373        let command_name = DefaultSlashCommand.name();
 374        self.editor.update(cx, |editor, cx| {
 375            editor.insert(&format!("/{command_name}\n\n"), window, cx)
 376        });
 377        let command = self.text_thread.update(cx, |text_thread, cx| {
 378            text_thread.reparse(cx);
 379            text_thread.parsed_slash_commands()[0].clone()
 380        });
 381        self.run_command(
 382            command.source_range,
 383            &command.name,
 384            &command.arguments,
 385            false,
 386            self.workspace.clone(),
 387            window,
 388            cx,
 389        );
 390    }
 391
 392    fn assist(&mut self, _: &Assist, window: &mut Window, cx: &mut Context<Self>) {
 393        if self.sending_disabled(cx) {
 394            return;
 395        }
 396        telemetry::event!("Agent Message Sent", agent = "zed-text");
 397        self.send_to_model(window, cx);
 398    }
 399
 400    fn send_to_model(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 401        self.last_error = None;
 402        if let Some(user_message) = self
 403            .text_thread
 404            .update(cx, |text_thread, cx| text_thread.assist(cx))
 405        {
 406            let new_selection = {
 407                let cursor = user_message
 408                    .start
 409                    .to_offset(self.text_thread.read(cx).buffer().read(cx));
 410                MultiBufferOffset(cursor)..MultiBufferOffset(cursor)
 411            };
 412            self.editor.update(cx, |editor, cx| {
 413                editor.change_selections(Default::default(), window, cx, |selections| {
 414                    selections.select_ranges([new_selection])
 415                });
 416            });
 417            // Avoid scrolling to the new cursor position so the assistant's output is stable.
 418            cx.defer_in(window, |this, _, _| this.scroll_position = None);
 419        }
 420
 421        cx.notify();
 422    }
 423
 424    fn cancel(
 425        &mut self,
 426        _: &editor::actions::Cancel,
 427        _window: &mut Window,
 428        cx: &mut Context<Self>,
 429    ) {
 430        self.last_error = None;
 431
 432        if self
 433            .text_thread
 434            .update(cx, |text_thread, cx| text_thread.cancel_last_assist(cx))
 435        {
 436            return;
 437        }
 438
 439        cx.propagate();
 440    }
 441
 442    fn cycle_message_role(
 443        &mut self,
 444        _: &CycleMessageRole,
 445        _window: &mut Window,
 446        cx: &mut Context<Self>,
 447    ) {
 448        let cursors = self.cursors(cx);
 449        self.text_thread.update(cx, |text_thread, cx| {
 450            let messages = text_thread
 451                .messages_for_offsets(cursors.into_iter().map(|cursor| cursor.0), cx)
 452                .into_iter()
 453                .map(|message| message.id)
 454                .collect();
 455            text_thread.cycle_message_roles(messages, cx)
 456        });
 457    }
 458
 459    fn cursors(&self, cx: &mut App) -> Vec<MultiBufferOffset> {
 460        let selections = self.editor.update(cx, |editor, cx| {
 461            editor
 462                .selections
 463                .all::<MultiBufferOffset>(&editor.display_snapshot(cx))
 464        });
 465        selections
 466            .into_iter()
 467            .map(|selection| selection.head())
 468            .collect()
 469    }
 470
 471    pub fn insert_command(&mut self, name: &str, window: &mut Window, cx: &mut Context<Self>) {
 472        if let Some(command) = self.slash_commands.command(name, cx) {
 473            self.editor.update(cx, |editor, cx| {
 474                editor.transact(window, cx, |editor, window, cx| {
 475                    editor.change_selections(Default::default(), window, cx, |s| s.try_cancel());
 476                    let snapshot = editor.buffer().read(cx).snapshot(cx);
 477                    let newest_cursor = editor
 478                        .selections
 479                        .newest::<Point>(&editor.display_snapshot(cx))
 480                        .head();
 481                    if newest_cursor.column > 0
 482                        || snapshot
 483                            .chars_at(newest_cursor)
 484                            .next()
 485                            .is_some_and(|ch| ch != '\n')
 486                    {
 487                        editor.move_to_end_of_line(
 488                            &MoveToEndOfLine {
 489                                stop_at_soft_wraps: false,
 490                            },
 491                            window,
 492                            cx,
 493                        );
 494                        editor.newline(&Newline, window, cx);
 495                    }
 496
 497                    editor.insert(&format!("/{name}"), window, cx);
 498                    if command.accepts_arguments() {
 499                        editor.insert(" ", window, cx);
 500                        editor.show_completions(&ShowCompletions, window, cx);
 501                    }
 502                });
 503            });
 504            if !command.requires_argument() {
 505                self.confirm_command(&ConfirmCommand, window, cx);
 506            }
 507        }
 508    }
 509
 510    pub fn confirm_command(
 511        &mut self,
 512        _: &ConfirmCommand,
 513        window: &mut Window,
 514        cx: &mut Context<Self>,
 515    ) {
 516        if self.editor.read(cx).has_visible_completions_menu() {
 517            return;
 518        }
 519
 520        let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
 521        let mut commands_by_range = HashMap::default();
 522        let workspace = self.workspace.clone();
 523        self.text_thread.update(cx, |text_thread, cx| {
 524            text_thread.reparse(cx);
 525            for selection in selections.iter() {
 526                if let Some(command) =
 527                    text_thread.pending_command_for_position(selection.head().text_anchor, cx)
 528                {
 529                    commands_by_range
 530                        .entry(command.source_range.clone())
 531                        .or_insert_with(|| command.clone());
 532                }
 533            }
 534        });
 535
 536        if commands_by_range.is_empty() {
 537            cx.propagate();
 538        } else {
 539            for command in commands_by_range.into_values() {
 540                self.run_command(
 541                    command.source_range,
 542                    &command.name,
 543                    &command.arguments,
 544                    true,
 545                    workspace.clone(),
 546                    window,
 547                    cx,
 548                );
 549            }
 550            cx.stop_propagation();
 551        }
 552    }
 553
 554    pub fn run_command(
 555        &mut self,
 556        command_range: Range<language::Anchor>,
 557        name: &str,
 558        arguments: &[String],
 559        ensure_trailing_newline: bool,
 560        workspace: WeakEntity<Workspace>,
 561        window: &mut Window,
 562        cx: &mut Context<Self>,
 563    ) {
 564        if let Some(command) = self.slash_commands.command(name, cx) {
 565            let text_thread = self.text_thread.read(cx);
 566            let sections = text_thread
 567                .slash_command_output_sections()
 568                .iter()
 569                .filter(|section| section.is_valid(text_thread.buffer().read(cx)))
 570                .cloned()
 571                .collect::<Vec<_>>();
 572            let snapshot = text_thread.buffer().read(cx).snapshot();
 573            let output = command.run(
 574                arguments,
 575                &sections,
 576                snapshot,
 577                workspace,
 578                self.lsp_adapter_delegate.clone(),
 579                window,
 580                cx,
 581            );
 582            self.text_thread.update(cx, |text_thread, cx| {
 583                text_thread.insert_command_output(
 584                    command_range,
 585                    name,
 586                    output,
 587                    ensure_trailing_newline,
 588                    cx,
 589                )
 590            });
 591        }
 592    }
 593
 594    fn handle_text_thread_event(
 595        &mut self,
 596        _: &Entity<TextThread>,
 597        event: &TextThreadEvent,
 598        window: &mut Window,
 599        cx: &mut Context<Self>,
 600    ) {
 601        let text_thread_editor = cx.entity().downgrade();
 602
 603        match event {
 604            TextThreadEvent::MessagesEdited => {
 605                self.update_message_headers(cx);
 606                self.update_image_blocks(cx);
 607                self.text_thread.update(cx, |text_thread, cx| {
 608                    text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
 609                });
 610            }
 611            TextThreadEvent::SummaryChanged => {
 612                cx.emit(EditorEvent::TitleChanged);
 613                self.text_thread.update(cx, |text_thread, cx| {
 614                    text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
 615                });
 616            }
 617            TextThreadEvent::SummaryGenerated => {}
 618            TextThreadEvent::PathChanged { .. } => {}
 619            TextThreadEvent::StartedThoughtProcess(range) => {
 620                let creases = self.insert_thought_process_output_sections(
 621                    [(
 622                        ThoughtProcessOutputSection {
 623                            range: range.clone(),
 624                        },
 625                        ThoughtProcessStatus::Pending,
 626                    )],
 627                    window,
 628                    cx,
 629                );
 630                self.pending_thought_process = Some((creases[0], range.start));
 631            }
 632            TextThreadEvent::EndedThoughtProcess(end) => {
 633                if let Some((crease_id, start)) = self.pending_thought_process.take() {
 634                    self.editor.update(cx, |editor, cx| {
 635                        let multi_buffer_snapshot = editor.buffer().read(cx).snapshot(cx);
 636                        let start_anchor =
 637                            multi_buffer_snapshot.as_singleton_anchor(start).unwrap();
 638
 639                        editor.display_map.update(cx, |display_map, cx| {
 640                            display_map.unfold_intersecting(
 641                                vec![start_anchor..start_anchor],
 642                                true,
 643                                cx,
 644                            );
 645                        });
 646                        editor.remove_creases(vec![crease_id], cx);
 647                    });
 648                    self.insert_thought_process_output_sections(
 649                        [(
 650                            ThoughtProcessOutputSection { range: start..*end },
 651                            ThoughtProcessStatus::Completed,
 652                        )],
 653                        window,
 654                        cx,
 655                    );
 656                }
 657            }
 658            TextThreadEvent::StreamedCompletion => {
 659                self.editor.update(cx, |editor, cx| {
 660                    if let Some(scroll_position) = self.scroll_position {
 661                        let snapshot = editor.snapshot(window, cx);
 662                        let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
 663                        let scroll_top =
 664                            cursor_point.row().as_f64() - scroll_position.offset_before_cursor.y;
 665                        editor.set_scroll_position(
 666                            point(scroll_position.offset_before_cursor.x, scroll_top),
 667                            window,
 668                            cx,
 669                        );
 670                    }
 671                });
 672            }
 673            TextThreadEvent::ParsedSlashCommandsUpdated { removed, updated } => {
 674                self.editor.update(cx, |editor, cx| {
 675                    let buffer = editor.buffer().read(cx).snapshot(cx);
 676                    let (&excerpt_id, _, _) = buffer.as_singleton().unwrap();
 677
 678                    editor.remove_creases(
 679                        removed
 680                            .iter()
 681                            .filter_map(|range| self.pending_slash_command_creases.remove(range)),
 682                        cx,
 683                    );
 684
 685                    let crease_ids = editor.insert_creases(
 686                        updated.iter().map(|command| {
 687                            let workspace = self.workspace.clone();
 688                            let confirm_command = Arc::new({
 689                                let text_thread_editor = text_thread_editor.clone();
 690                                let command = command.clone();
 691                                move |window: &mut Window, cx: &mut App| {
 692                                    text_thread_editor
 693                                        .update(cx, |text_thread_editor, cx| {
 694                                            text_thread_editor.run_command(
 695                                                command.source_range.clone(),
 696                                                &command.name,
 697                                                &command.arguments,
 698                                                false,
 699                                                workspace.clone(),
 700                                                window,
 701                                                cx,
 702                                            );
 703                                        })
 704                                        .ok();
 705                                }
 706                            });
 707                            let placeholder = FoldPlaceholder {
 708                                render: Arc::new(move |_, _, _| Empty.into_any()),
 709                                ..Default::default()
 710                            };
 711                            let render_toggle = {
 712                                let confirm_command = confirm_command.clone();
 713                                let command = command.clone();
 714                                move |row, _, _, _window: &mut Window, _cx: &mut App| {
 715                                    render_pending_slash_command_gutter_decoration(
 716                                        row,
 717                                        &command.status,
 718                                        confirm_command.clone(),
 719                                    )
 720                                }
 721                            };
 722                            let render_trailer = {
 723                                move |_row, _unfold, _window: &mut Window, _cx: &mut App| {
 724                                    Empty.into_any()
 725                                }
 726                            };
 727
 728                            let range = buffer
 729                                .anchor_range_in_excerpt(excerpt_id, command.source_range.clone())
 730                                .unwrap();
 731                            Crease::inline(range, placeholder, render_toggle, render_trailer)
 732                        }),
 733                        cx,
 734                    );
 735
 736                    self.pending_slash_command_creases.extend(
 737                        updated
 738                            .iter()
 739                            .map(|command| command.source_range.clone())
 740                            .zip(crease_ids),
 741                    );
 742                })
 743            }
 744            TextThreadEvent::InvokedSlashCommandChanged { command_id } => {
 745                self.update_invoked_slash_command(*command_id, window, cx);
 746            }
 747            TextThreadEvent::SlashCommandOutputSectionAdded { section } => {
 748                self.insert_slash_command_output_sections([section.clone()], false, window, cx);
 749            }
 750            TextThreadEvent::Operation(_) => {}
 751            TextThreadEvent::ShowAssistError(error_message) => {
 752                self.last_error = Some(AssistError::Message(error_message.clone()));
 753            }
 754            TextThreadEvent::ShowPaymentRequiredError => {
 755                self.last_error = Some(AssistError::PaymentRequired);
 756            }
 757        }
 758    }
 759
 760    fn update_invoked_slash_command(
 761        &mut self,
 762        command_id: InvokedSlashCommandId,
 763        window: &mut Window,
 764        cx: &mut Context<Self>,
 765    ) {
 766        if let Some(invoked_slash_command) =
 767            self.text_thread.read(cx).invoked_slash_command(&command_id)
 768            && let InvokedSlashCommandStatus::Finished = invoked_slash_command.status
 769        {
 770            let run_commands_in_ranges = invoked_slash_command.run_commands_in_ranges.clone();
 771            for range in run_commands_in_ranges {
 772                let commands = self.text_thread.update(cx, |text_thread, cx| {
 773                    text_thread.reparse(cx);
 774                    text_thread
 775                        .pending_commands_for_range(range.clone(), cx)
 776                        .to_vec()
 777                });
 778
 779                for command in commands {
 780                    self.run_command(
 781                        command.source_range,
 782                        &command.name,
 783                        &command.arguments,
 784                        false,
 785                        self.workspace.clone(),
 786                        window,
 787                        cx,
 788                    );
 789                }
 790            }
 791        }
 792
 793        self.editor.update(cx, |editor, cx| {
 794            if let Some(invoked_slash_command) =
 795                self.text_thread.read(cx).invoked_slash_command(&command_id)
 796            {
 797                if let InvokedSlashCommandStatus::Finished = invoked_slash_command.status {
 798                    let buffer = editor.buffer().read(cx).snapshot(cx);
 799                    let (&excerpt_id, _buffer_id, _buffer_snapshot) =
 800                        buffer.as_singleton().unwrap();
 801
 802                    let range = buffer
 803                        .anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
 804                        .unwrap();
 805                    editor.remove_folds_with_type(
 806                        &[range],
 807                        TypeId::of::<PendingSlashCommand>(),
 808                        false,
 809                        cx,
 810                    );
 811
 812                    editor.remove_creases(
 813                        HashSet::from_iter(self.invoked_slash_command_creases.remove(&command_id)),
 814                        cx,
 815                    );
 816                } else if let hash_map::Entry::Vacant(entry) =
 817                    self.invoked_slash_command_creases.entry(command_id)
 818                {
 819                    let buffer = editor.buffer().read(cx).snapshot(cx);
 820                    let (&excerpt_id, _buffer_id, _buffer_snapshot) =
 821                        buffer.as_singleton().unwrap();
 822                    let context = self.text_thread.downgrade();
 823                    let range = buffer
 824                        .anchor_range_in_excerpt(excerpt_id, invoked_slash_command.range.clone())
 825                        .unwrap();
 826                    let crease = Crease::inline(
 827                        range,
 828                        invoked_slash_command_fold_placeholder(command_id, context),
 829                        fold_toggle("invoked-slash-command"),
 830                        |_row, _folded, _window, _cx| Empty.into_any(),
 831                    );
 832                    let crease_ids = editor.insert_creases([crease.clone()], cx);
 833                    editor.fold_creases(vec![crease], false, window, cx);
 834                    entry.insert(crease_ids[0]);
 835                } else {
 836                    cx.notify()
 837                }
 838            } else {
 839                editor.remove_creases(
 840                    HashSet::from_iter(self.invoked_slash_command_creases.remove(&command_id)),
 841                    cx,
 842                );
 843                cx.notify();
 844            };
 845        });
 846    }
 847
 848    fn insert_thought_process_output_sections(
 849        &mut self,
 850        sections: impl IntoIterator<
 851            Item = (
 852                ThoughtProcessOutputSection<language::Anchor>,
 853                ThoughtProcessStatus,
 854            ),
 855        >,
 856        window: &mut Window,
 857        cx: &mut Context<Self>,
 858    ) -> Vec<CreaseId> {
 859        self.editor.update(cx, |editor, cx| {
 860            let buffer = editor.buffer().read(cx).snapshot(cx);
 861            let excerpt_id = *buffer.as_singleton().unwrap().0;
 862            let mut buffer_rows_to_fold = BTreeSet::new();
 863            let mut creases = Vec::new();
 864            for (section, status) in sections {
 865                let range = buffer
 866                    .anchor_range_in_excerpt(excerpt_id, section.range)
 867                    .unwrap();
 868                let buffer_row = MultiBufferRow(range.start.to_point(&buffer).row);
 869                buffer_rows_to_fold.insert(buffer_row);
 870                creases.push(
 871                    Crease::inline(
 872                        range,
 873                        FoldPlaceholder {
 874                            render: render_thought_process_fold_icon_button(
 875                                cx.entity().downgrade(),
 876                                status,
 877                            ),
 878                            merge_adjacent: false,
 879                            ..Default::default()
 880                        },
 881                        render_slash_command_output_toggle,
 882                        |_, _, _, _| Empty.into_any_element(),
 883                    )
 884                    .with_metadata(CreaseMetadata {
 885                        icon_path: SharedString::from(IconName::Ai.path()),
 886                        label: "Thinking Process".into(),
 887                    }),
 888                );
 889            }
 890
 891            let creases = editor.insert_creases(creases, cx);
 892
 893            for buffer_row in buffer_rows_to_fold.into_iter().rev() {
 894                editor.fold_at(buffer_row, window, cx);
 895            }
 896
 897            creases
 898        })
 899    }
 900
 901    fn insert_slash_command_output_sections(
 902        &mut self,
 903        sections: impl IntoIterator<Item = SlashCommandOutputSection<language::Anchor>>,
 904        expand_result: bool,
 905        window: &mut Window,
 906        cx: &mut Context<Self>,
 907    ) {
 908        self.editor.update(cx, |editor, cx| {
 909            let buffer = editor.buffer().read(cx).snapshot(cx);
 910            let excerpt_id = *buffer.as_singleton().unwrap().0;
 911            let mut buffer_rows_to_fold = BTreeSet::new();
 912            let mut creases = Vec::new();
 913            for section in sections {
 914                let range = buffer
 915                    .anchor_range_in_excerpt(excerpt_id, section.range)
 916                    .unwrap();
 917                let buffer_row = MultiBufferRow(range.start.to_point(&buffer).row);
 918                buffer_rows_to_fold.insert(buffer_row);
 919                creases.push(
 920                    Crease::inline(
 921                        range,
 922                        FoldPlaceholder {
 923                            render: render_fold_icon_button(
 924                                cx.entity().downgrade(),
 925                                section.icon.path().into(),
 926                                section.label.clone(),
 927                            ),
 928                            merge_adjacent: false,
 929                            ..Default::default()
 930                        },
 931                        render_slash_command_output_toggle,
 932                        |_, _, _, _| Empty.into_any_element(),
 933                    )
 934                    .with_metadata(CreaseMetadata {
 935                        icon_path: section.icon.path().into(),
 936                        label: section.label,
 937                    }),
 938                );
 939            }
 940
 941            editor.insert_creases(creases, cx);
 942
 943            if expand_result {
 944                buffer_rows_to_fold.clear();
 945            }
 946            for buffer_row in buffer_rows_to_fold.into_iter().rev() {
 947                editor.fold_at(buffer_row, window, cx);
 948            }
 949        });
 950    }
 951
 952    fn handle_editor_event(
 953        &mut self,
 954        _: &Entity<Editor>,
 955        event: &EditorEvent,
 956        window: &mut Window,
 957        cx: &mut Context<Self>,
 958    ) {
 959        match event {
 960            EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
 961                let cursor_scroll_position = self.cursor_scroll_position(window, cx);
 962                if *autoscroll {
 963                    self.scroll_position = cursor_scroll_position;
 964                } else if self.scroll_position != cursor_scroll_position {
 965                    self.scroll_position = None;
 966                }
 967            }
 968            EditorEvent::SelectionsChanged { .. } => {
 969                self.scroll_position = self.cursor_scroll_position(window, cx);
 970            }
 971            _ => {}
 972        }
 973        cx.emit(event.clone());
 974    }
 975
 976    fn handle_editor_search_event(
 977        &mut self,
 978        _: &Entity<Editor>,
 979        event: &SearchEvent,
 980        _window: &mut Window,
 981        cx: &mut Context<Self>,
 982    ) {
 983        cx.emit(event.clone());
 984    }
 985
 986    fn cursor_scroll_position(
 987        &self,
 988        window: &mut Window,
 989        cx: &mut Context<Self>,
 990    ) -> Option<ScrollPosition> {
 991        self.editor.update(cx, |editor, cx| {
 992            let snapshot = editor.snapshot(window, cx);
 993            let cursor = editor.selections.newest_anchor().head();
 994            let cursor_row = cursor
 995                .to_display_point(&snapshot.display_snapshot)
 996                .row()
 997                .as_f64();
 998            let scroll_position = editor
 999                .scroll_manager
1000                .anchor()
1001                .scroll_position(&snapshot.display_snapshot);
1002
1003            let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
1004            if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
1005                Some(ScrollPosition {
1006                    cursor,
1007                    offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
1008                })
1009            } else {
1010                None
1011            }
1012        })
1013    }
1014
1015    fn esc_kbd(cx: &App) -> Div {
1016        let colors = cx.theme().colors().clone();
1017
1018        h_flex()
1019            .items_center()
1020            .gap_1()
1021            .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
1022            .text_size(TextSize::XSmall.rems(cx))
1023            .text_color(colors.text_muted)
1024            .child("Press")
1025            .child(
1026                h_flex()
1027                    .rounded_sm()
1028                    .px_1()
1029                    .mr_0p5()
1030                    .border_1()
1031                    .border_color(colors.border_variant.alpha(0.6))
1032                    .bg(colors.element_background.alpha(0.6))
1033                    .child("esc"),
1034            )
1035            .child("to cancel")
1036    }
1037
1038    fn update_message_headers(&mut self, cx: &mut Context<Self>) {
1039        self.editor.update(cx, |editor, cx| {
1040            let buffer = editor.buffer().read(cx).snapshot(cx);
1041
1042            let excerpt_id = *buffer.as_singleton().unwrap().0;
1043            let mut old_blocks = std::mem::take(&mut self.blocks);
1044            let mut blocks_to_remove: HashMap<_, _> = old_blocks
1045                .iter()
1046                .map(|(message_id, (_, block_id))| (*message_id, *block_id))
1047                .collect();
1048            let mut blocks_to_replace: HashMap<_, RenderBlock> = Default::default();
1049
1050            let render_block = |message: MessageMetadata| -> RenderBlock {
1051                Arc::new({
1052                    let text_thread = self.text_thread.clone();
1053
1054                    move |cx| {
1055                        let message_id = MessageId(message.timestamp);
1056                        let llm_loading = message.role == Role::Assistant
1057                            && message.status == MessageStatus::Pending;
1058
1059                        let (label, spinner, note) = match message.role {
1060                            Role::User => (
1061                                Label::new("You").color(Color::Default).into_any_element(),
1062                                None,
1063                                None,
1064                            ),
1065                            Role::Assistant => {
1066                                let base_label = Label::new("Agent").color(Color::Info);
1067                                let mut spinner = None;
1068                                let mut note = None;
1069                                let animated_label = if llm_loading {
1070                                    base_label
1071                                        .with_animation(
1072                                            "pulsating-label",
1073                                            Animation::new(Duration::from_secs(2))
1074                                                .repeat()
1075                                                .with_easing(pulsating_between(0.4, 0.8)),
1076                                            |label, delta| label.alpha(delta),
1077                                        )
1078                                        .into_any_element()
1079                                } else {
1080                                    base_label.into_any_element()
1081                                };
1082                                if llm_loading {
1083                                    spinner = Some(
1084                                        Icon::new(IconName::ArrowCircle)
1085                                            .size(IconSize::XSmall)
1086                                            .color(Color::Info)
1087                                            .with_rotate_animation(2)
1088                                            .into_any_element(),
1089                                    );
1090                                    note = Some(Self::esc_kbd(cx).into_any_element());
1091                                }
1092                                (animated_label, spinner, note)
1093                            }
1094                            Role::System => (
1095                                Label::new("System")
1096                                    .color(Color::Warning)
1097                                    .into_any_element(),
1098                                None,
1099                                None,
1100                            ),
1101                        };
1102
1103                        let sender = h_flex()
1104                            .items_center()
1105                            .gap_2p5()
1106                            .child(
1107                                ButtonLike::new("role")
1108                                    .style(ButtonStyle::Filled)
1109                                    .child(
1110                                        h_flex()
1111                                            .items_center()
1112                                            .gap_1p5()
1113                                            .child(label)
1114                                            .children(spinner),
1115                                    )
1116                                    .tooltip(|_window, cx| {
1117                                        Tooltip::with_meta(
1118                                            "Toggle message role",
1119                                            None,
1120                                            "Available roles: You (User), Agent, System",
1121                                            cx,
1122                                        )
1123                                    })
1124                                    .on_click({
1125                                        let text_thread = text_thread.clone();
1126                                        move |_, _window, cx| {
1127                                            text_thread.update(cx, |text_thread, cx| {
1128                                                text_thread.cycle_message_roles(
1129                                                    HashSet::from_iter(Some(message_id)),
1130                                                    cx,
1131                                                )
1132                                            })
1133                                        }
1134                                    }),
1135                            )
1136                            .children(note);
1137
1138                        h_flex()
1139                            .id(("message_header", message_id.as_u64()))
1140                            .pl(cx.margins.gutter.full_width())
1141                            .h_11()
1142                            .w_full()
1143                            .relative()
1144                            .gap_1p5()
1145                            .child(sender)
1146                            .children(match &message.cache {
1147                                Some(cache) if cache.is_final_anchor => match cache.status {
1148                                    CacheStatus::Cached => Some(
1149                                        div()
1150                                            .id("cached")
1151                                            .child(
1152                                                Icon::new(IconName::DatabaseZap)
1153                                                    .size(IconSize::XSmall)
1154                                                    .color(Color::Hint),
1155                                            )
1156                                            .tooltip(|_window, cx| {
1157                                                Tooltip::with_meta(
1158                                                    "Context Cached",
1159                                                    None,
1160                                                    "Large messages cached to optimize performance",
1161                                                    cx,
1162                                                )
1163                                            })
1164                                            .into_any_element(),
1165                                    ),
1166                                    CacheStatus::Pending => Some(
1167                                        div()
1168                                            .child(
1169                                                Icon::new(IconName::Ellipsis)
1170                                                    .size(IconSize::XSmall)
1171                                                    .color(Color::Hint),
1172                                            )
1173                                            .into_any_element(),
1174                                    ),
1175                                },
1176                                _ => None,
1177                            })
1178                            .children(match &message.status {
1179                                MessageStatus::Error(error) => Some(
1180                                    Button::new("show-error", "Error")
1181                                        .color(Color::Error)
1182                                        .selected_label_color(Color::Error)
1183                                        .selected_icon_color(Color::Error)
1184                                        .icon(IconName::XCircle)
1185                                        .icon_color(Color::Error)
1186                                        .icon_size(IconSize::XSmall)
1187                                        .icon_position(IconPosition::Start)
1188                                        .tooltip(Tooltip::text("View Details"))
1189                                        .on_click({
1190                                            let text_thread = text_thread.clone();
1191                                            let error = error.clone();
1192                                            move |_, _window, cx| {
1193                                                text_thread.update(cx, |_, cx| {
1194                                                    cx.emit(TextThreadEvent::ShowAssistError(
1195                                                        error.clone(),
1196                                                    ));
1197                                                });
1198                                            }
1199                                        })
1200                                        .into_any_element(),
1201                                ),
1202                                MessageStatus::Canceled => Some(
1203                                    h_flex()
1204                                        .gap_1()
1205                                        .items_center()
1206                                        .child(
1207                                            Icon::new(IconName::XCircle)
1208                                                .color(Color::Disabled)
1209                                                .size(IconSize::XSmall),
1210                                        )
1211                                        .child(
1212                                            Label::new("Canceled")
1213                                                .size(LabelSize::Small)
1214                                                .color(Color::Disabled),
1215                                        )
1216                                        .into_any_element(),
1217                                ),
1218                                _ => None,
1219                            })
1220                            .into_any_element()
1221                    }
1222                })
1223            };
1224            let create_block_properties = |message: &Message| BlockProperties {
1225                height: Some(2),
1226                style: BlockStyle::Sticky,
1227                placement: BlockPlacement::Above(
1228                    buffer
1229                        .anchor_in_excerpt(excerpt_id, message.anchor_range.start)
1230                        .unwrap(),
1231                ),
1232                priority: usize::MAX,
1233                render: render_block(MessageMetadata::from(message)),
1234            };
1235            let mut new_blocks = vec![];
1236            let mut block_index_to_message = vec![];
1237            for message in self.text_thread.read(cx).messages(cx) {
1238                if blocks_to_remove.remove(&message.id).is_some() {
1239                    // This is an old message that we might modify.
1240                    let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
1241                        debug_assert!(
1242                            false,
1243                            "old_blocks should contain a message_id we've just removed."
1244                        );
1245                        continue;
1246                    };
1247                    // Should we modify it?
1248                    let message_meta = MessageMetadata::from(&message);
1249                    if meta != &message_meta {
1250                        blocks_to_replace.insert(*block_id, render_block(message_meta.clone()));
1251                        *meta = message_meta;
1252                    }
1253                } else {
1254                    // This is a new message.
1255                    new_blocks.push(create_block_properties(&message));
1256                    block_index_to_message.push((message.id, MessageMetadata::from(&message)));
1257                }
1258            }
1259            editor.replace_blocks(blocks_to_replace, None, cx);
1260            editor.remove_blocks(blocks_to_remove.into_values().collect(), None, cx);
1261
1262            let ids = editor.insert_blocks(new_blocks, None, cx);
1263            old_blocks.extend(ids.into_iter().zip(block_index_to_message).map(
1264                |(block_id, (message_id, message_meta))| (message_id, (message_meta, block_id)),
1265            ));
1266            self.blocks = old_blocks;
1267        });
1268    }
1269
1270    /// Returns either the selected text, or the content of the Markdown code
1271    /// block surrounding the cursor.
1272    fn get_selection_or_code_block(
1273        context_editor_view: &Entity<TextThreadEditor>,
1274        cx: &mut Context<Workspace>,
1275    ) -> Option<(String, bool)> {
1276        const CODE_FENCE_DELIMITER: &str = "```";
1277
1278        let text_thread_editor = context_editor_view.read(cx).editor.clone();
1279        text_thread_editor.update(cx, |text_thread_editor, cx| {
1280            let display_map = text_thread_editor.display_snapshot(cx);
1281            if text_thread_editor
1282                .selections
1283                .newest::<Point>(&display_map)
1284                .is_empty()
1285            {
1286                let snapshot = text_thread_editor.buffer().read(cx).snapshot(cx);
1287                let (_, _, snapshot) = snapshot.as_singleton()?;
1288
1289                let head = text_thread_editor
1290                    .selections
1291                    .newest::<Point>(&display_map)
1292                    .head();
1293                let offset = snapshot.point_to_offset(head);
1294
1295                let surrounding_code_block_range = find_surrounding_code_block(snapshot, offset)?;
1296                let mut text = snapshot
1297                    .text_for_range(surrounding_code_block_range)
1298                    .collect::<String>();
1299
1300                // If there is no newline trailing the closing three-backticks, then
1301                // tree-sitter-md extends the range of the content node to include
1302                // the backticks.
1303                if text.ends_with(CODE_FENCE_DELIMITER) {
1304                    text.drain((text.len() - CODE_FENCE_DELIMITER.len())..);
1305                }
1306
1307                (!text.is_empty()).then_some((text, true))
1308            } else {
1309                let selection = text_thread_editor.selections.newest_adjusted(&display_map);
1310                let buffer = text_thread_editor.buffer().read(cx).snapshot(cx);
1311                let selected_text = buffer.text_for_range(selection.range()).collect::<String>();
1312
1313                (!selected_text.is_empty()).then_some((selected_text, false))
1314            }
1315        })
1316    }
1317
1318    pub fn insert_selection(
1319        workspace: &mut Workspace,
1320        _: &InsertIntoEditor,
1321        window: &mut Window,
1322        cx: &mut Context<Workspace>,
1323    ) {
1324        let Some(agent_panel_delegate) = <dyn AgentPanelDelegate>::try_global(cx) else {
1325            return;
1326        };
1327        let Some(context_editor_view) =
1328            agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
1329        else {
1330            return;
1331        };
1332        let Some(active_editor_view) = workspace
1333            .active_item(cx)
1334            .and_then(|item| item.act_as::<Editor>(cx))
1335        else {
1336            return;
1337        };
1338
1339        if let Some((text, _)) = Self::get_selection_or_code_block(&context_editor_view, cx) {
1340            active_editor_view.update(cx, |editor, cx| {
1341                editor.insert(&text, window, cx);
1342                editor.focus_handle(cx).focus(window);
1343            })
1344        }
1345    }
1346
1347    pub fn copy_code(
1348        workspace: &mut Workspace,
1349        _: &CopyCode,
1350        window: &mut Window,
1351        cx: &mut Context<Workspace>,
1352    ) {
1353        let result = maybe!({
1354            let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
1355            let context_editor_view =
1356                agent_panel_delegate.active_text_thread_editor(workspace, window, cx)?;
1357            Self::get_selection_or_code_block(&context_editor_view, cx)
1358        });
1359        let Some((text, is_code_block)) = result else {
1360            return;
1361        };
1362
1363        cx.write_to_clipboard(ClipboardItem::new_string(text));
1364
1365        struct CopyToClipboardToast;
1366        workspace.show_toast(
1367            Toast::new(
1368                NotificationId::unique::<CopyToClipboardToast>(),
1369                format!(
1370                    "{} copied to clipboard.",
1371                    if is_code_block {
1372                        "Code block"
1373                    } else {
1374                        "Selection"
1375                    }
1376                ),
1377            )
1378            .autohide(),
1379            cx,
1380        );
1381    }
1382
1383    pub fn handle_insert_dragged_files(
1384        workspace: &mut Workspace,
1385        action: &InsertDraggedFiles,
1386        window: &mut Window,
1387        cx: &mut Context<Workspace>,
1388    ) {
1389        let Some(agent_panel_delegate) = <dyn AgentPanelDelegate>::try_global(cx) else {
1390            return;
1391        };
1392        let Some(context_editor_view) =
1393            agent_panel_delegate.active_text_thread_editor(workspace, window, cx)
1394        else {
1395            return;
1396        };
1397
1398        let project = context_editor_view.read(cx).project.clone();
1399
1400        let paths = match action {
1401            InsertDraggedFiles::ProjectPaths(paths) => Task::ready((paths.clone(), vec![])),
1402            InsertDraggedFiles::ExternalFiles(paths) => {
1403                let tasks = paths
1404                    .clone()
1405                    .into_iter()
1406                    .map(|path| Workspace::project_path_for_path(project.clone(), &path, false, cx))
1407                    .collect::<Vec<_>>();
1408
1409                cx.background_spawn(async move {
1410                    let mut paths = vec![];
1411                    let mut worktrees = vec![];
1412
1413                    let opened_paths = futures::future::join_all(tasks).await;
1414
1415                    for entry in opened_paths {
1416                        if let Some((worktree, project_path)) = entry.log_err() {
1417                            worktrees.push(worktree);
1418                            paths.push(project_path);
1419                        }
1420                    }
1421
1422                    (paths, worktrees)
1423                })
1424            }
1425        };
1426
1427        context_editor_view.update(cx, |_, cx| {
1428            cx.spawn_in(window, async move |this, cx| {
1429                let (paths, dragged_file_worktrees) = paths.await;
1430                this.update_in(cx, |this, window, cx| {
1431                    this.insert_dragged_files(paths, dragged_file_worktrees, window, cx);
1432                })
1433                .ok();
1434            })
1435            .detach();
1436        })
1437    }
1438
1439    pub fn insert_dragged_files(
1440        &mut self,
1441        opened_paths: Vec<ProjectPath>,
1442        added_worktrees: Vec<Entity<Worktree>>,
1443        window: &mut Window,
1444        cx: &mut Context<Self>,
1445    ) {
1446        let mut file_slash_command_args = vec![];
1447        for project_path in opened_paths.into_iter() {
1448            let Some(worktree) = self
1449                .project
1450                .read(cx)
1451                .worktree_for_id(project_path.worktree_id, cx)
1452            else {
1453                continue;
1454            };
1455            let path_style = worktree.read(cx).path_style();
1456            let full_path = worktree
1457                .read(cx)
1458                .root_name()
1459                .join(&project_path.path)
1460                .display(path_style)
1461                .into_owned();
1462            file_slash_command_args.push(full_path);
1463        }
1464
1465        let cmd_name = FileSlashCommand.name();
1466
1467        let file_argument = file_slash_command_args.join(" ");
1468
1469        self.editor.update(cx, |editor, cx| {
1470            editor.insert("\n", window, cx);
1471            editor.insert(&format!("/{} {}", cmd_name, file_argument), window, cx);
1472        });
1473        self.confirm_command(&ConfirmCommand, window, cx);
1474        self.dragged_file_worktrees.extend(added_worktrees);
1475    }
1476
1477    pub fn quote_selection(
1478        workspace: &mut Workspace,
1479        _: &AddSelectionToThread,
1480        window: &mut Window,
1481        cx: &mut Context<Workspace>,
1482    ) {
1483        let Some(agent_panel_delegate) = <dyn AgentPanelDelegate>::try_global(cx) else {
1484            return;
1485        };
1486
1487        let Some((selections, buffer)) = maybe!({
1488            let editor = workspace
1489                .active_item(cx)
1490                .and_then(|item| item.act_as::<Editor>(cx))?;
1491
1492            let buffer = editor.read(cx).buffer().clone();
1493            let snapshot = buffer.read(cx).snapshot(cx);
1494            let selections = editor.update(cx, |editor, cx| {
1495                editor
1496                    .selections
1497                    .all_adjusted(&editor.display_snapshot(cx))
1498                    .into_iter()
1499                    .filter_map(|s| {
1500                        (!s.is_empty())
1501                            .then(|| snapshot.anchor_after(s.start)..snapshot.anchor_before(s.end))
1502                    })
1503                    .collect::<Vec<_>>()
1504            });
1505            Some((selections, buffer))
1506        }) else {
1507            return;
1508        };
1509
1510        if selections.is_empty() {
1511            return;
1512        }
1513
1514        agent_panel_delegate.quote_selection(workspace, selections, buffer, window, cx);
1515    }
1516
1517    pub fn quote_ranges(
1518        &mut self,
1519        ranges: Vec<Range<Point>>,
1520        snapshot: MultiBufferSnapshot,
1521        window: &mut Window,
1522        cx: &mut Context<Self>,
1523    ) {
1524        let creases = selections_creases(ranges, snapshot, cx);
1525
1526        self.editor.update(cx, |editor, cx| {
1527            editor.insert("\n", window, cx);
1528            for (text, crease_title) in creases {
1529                let point = editor
1530                    .selections
1531                    .newest::<Point>(&editor.display_snapshot(cx))
1532                    .head();
1533                let start_row = MultiBufferRow(point.row);
1534
1535                editor.insert(&text, window, cx);
1536
1537                let snapshot = editor.buffer().read(cx).snapshot(cx);
1538                let anchor_before = snapshot.anchor_after(point);
1539                let anchor_after = editor
1540                    .selections
1541                    .newest_anchor()
1542                    .head()
1543                    .bias_left(&snapshot);
1544
1545                editor.insert("\n", window, cx);
1546
1547                let fold_placeholder =
1548                    quote_selection_fold_placeholder(crease_title, cx.entity().downgrade());
1549                let crease = Crease::inline(
1550                    anchor_before..anchor_after,
1551                    fold_placeholder,
1552                    render_quote_selection_output_toggle,
1553                    |_, _, _, _| Empty.into_any(),
1554                );
1555                editor.insert_creases(vec![crease], cx);
1556                editor.fold_at(start_row, window, cx);
1557            }
1558        })
1559    }
1560
1561    fn copy(&mut self, _: &editor::actions::Copy, _window: &mut Window, cx: &mut Context<Self>) {
1562        if self.editor.read(cx).selections.count() == 1 {
1563            let (copied_text, metadata, _) = self.get_clipboard_contents(cx);
1564            cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
1565                copied_text,
1566                metadata,
1567            ));
1568            cx.stop_propagation();
1569            return;
1570        }
1571
1572        cx.propagate();
1573    }
1574
1575    fn cut(&mut self, _: &editor::actions::Cut, window: &mut Window, cx: &mut Context<Self>) {
1576        if self.editor.read(cx).selections.count() == 1 {
1577            let (copied_text, metadata, selections) = self.get_clipboard_contents(cx);
1578
1579            self.editor.update(cx, |editor, cx| {
1580                editor.transact(window, cx, |this, window, cx| {
1581                    this.change_selections(Default::default(), window, cx, |s| {
1582                        s.select(selections);
1583                    });
1584                    this.insert("", window, cx);
1585                    cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
1586                        copied_text,
1587                        metadata,
1588                    ));
1589                });
1590            });
1591
1592            cx.stop_propagation();
1593            return;
1594        }
1595
1596        cx.propagate();
1597    }
1598
1599    fn get_clipboard_contents(
1600        &mut self,
1601        cx: &mut Context<Self>,
1602    ) -> (
1603        String,
1604        CopyMetadata,
1605        Vec<text::Selection<MultiBufferOffset>>,
1606    ) {
1607        let (mut selection, creases) = self.editor.update(cx, |editor, cx| {
1608            let mut selection = editor
1609                .selections
1610                .newest_adjusted(&editor.display_snapshot(cx));
1611            let snapshot = editor.buffer().read(cx).snapshot(cx);
1612
1613            selection.goal = SelectionGoal::None;
1614
1615            let selection_start = snapshot.point_to_offset(selection.start);
1616
1617            (
1618                selection.map(|point| snapshot.point_to_offset(point)),
1619                editor.display_map.update(cx, |display_map, cx| {
1620                    display_map
1621                        .snapshot(cx)
1622                        .crease_snapshot
1623                        .creases_in_range(
1624                            MultiBufferRow(selection.start.row)
1625                                ..MultiBufferRow(selection.end.row + 1),
1626                            &snapshot,
1627                        )
1628                        .filter_map(|crease| {
1629                            if let Crease::Inline {
1630                                range, metadata, ..
1631                            } = &crease
1632                            {
1633                                let metadata = metadata.as_ref()?;
1634                                let start = range
1635                                    .start
1636                                    .to_offset(&snapshot)
1637                                    .saturating_sub(selection_start);
1638                                let end = range
1639                                    .end
1640                                    .to_offset(&snapshot)
1641                                    .saturating_sub(selection_start);
1642
1643                                let range_relative_to_selection = start..end;
1644                                if !range_relative_to_selection.is_empty() {
1645                                    return Some(SelectedCreaseMetadata {
1646                                        range_relative_to_selection,
1647                                        crease: metadata.clone(),
1648                                    });
1649                                }
1650                            }
1651                            None
1652                        })
1653                        .collect::<Vec<_>>()
1654                }),
1655            )
1656        });
1657
1658        let text_thread = self.text_thread.read(cx);
1659
1660        let mut text = String::new();
1661
1662        // If selection is empty, we want to copy the entire line
1663        if selection.range().is_empty() {
1664            let snapshot = self.editor.read(cx).buffer().read(cx).snapshot(cx);
1665            let point = snapshot.offset_to_point(selection.range().start);
1666            selection.start = snapshot.point_to_offset(Point::new(point.row, 0));
1667            selection.end = snapshot
1668                .point_to_offset(cmp::min(Point::new(point.row + 1, 0), snapshot.max_point()));
1669            for chunk in snapshot.text_for_range(selection.range()) {
1670                text.push_str(chunk);
1671            }
1672        } else {
1673            for message in text_thread.messages(cx) {
1674                if message.offset_range.start >= selection.range().end.0 {
1675                    break;
1676                } else if message.offset_range.end >= selection.range().start.0 {
1677                    let range = cmp::max(message.offset_range.start, selection.range().start.0)
1678                        ..cmp::min(message.offset_range.end, selection.range().end.0);
1679                    if !range.is_empty() {
1680                        for chunk in text_thread.buffer().read(cx).text_for_range(range) {
1681                            text.push_str(chunk);
1682                        }
1683                        if message.offset_range.end < selection.range().end.0 {
1684                            text.push('\n');
1685                        }
1686                    }
1687                }
1688            }
1689        }
1690        (text, CopyMetadata { creases }, vec![selection])
1691    }
1692
1693    fn paste(
1694        &mut self,
1695        action: &editor::actions::Paste,
1696        window: &mut Window,
1697        cx: &mut Context<Self>,
1698    ) {
1699        let editor_clipboard_selections = cx
1700            .read_from_clipboard()
1701            .and_then(|item| item.entries().first().cloned())
1702            .and_then(|entry| match entry {
1703                ClipboardEntry::String(text) => {
1704                    text.metadata_json::<Vec<editor::ClipboardSelection>>()
1705                }
1706                _ => None,
1707            });
1708
1709        let has_file_context = editor_clipboard_selections
1710            .as_ref()
1711            .is_some_and(|selections| {
1712                selections
1713                    .iter()
1714                    .any(|sel| sel.file_path.is_some() && sel.line_range.is_some())
1715            });
1716
1717        if has_file_context {
1718            if let Some(clipboard_item) = cx.read_from_clipboard() {
1719                if let Some(ClipboardEntry::String(clipboard_text)) =
1720                    clipboard_item.entries().first()
1721                {
1722                    if let Some(selections) = editor_clipboard_selections {
1723                        cx.stop_propagation();
1724
1725                        let text = clipboard_text.text();
1726                        self.editor.update(cx, |editor, cx| {
1727                            let mut current_offset = 0;
1728                            let weak_editor = cx.entity().downgrade();
1729
1730                            for selection in selections {
1731                                if let (Some(file_path), Some(line_range)) =
1732                                    (selection.file_path, selection.line_range)
1733                                {
1734                                    let selected_text =
1735                                        &text[current_offset..current_offset + selection.len];
1736                                    let fence = assistant_slash_commands::codeblock_fence_for_path(
1737                                        file_path.to_str(),
1738                                        Some(line_range.clone()),
1739                                    );
1740                                    let formatted_text = format!("{fence}{selected_text}\n```");
1741
1742                                    let insert_point = editor
1743                                        .selections
1744                                        .newest::<Point>(&editor.display_snapshot(cx))
1745                                        .head();
1746                                    let start_row = MultiBufferRow(insert_point.row);
1747
1748                                    editor.insert(&formatted_text, window, cx);
1749
1750                                    let snapshot = editor.buffer().read(cx).snapshot(cx);
1751                                    let anchor_before = snapshot.anchor_after(insert_point);
1752                                    let anchor_after = editor
1753                                        .selections
1754                                        .newest_anchor()
1755                                        .head()
1756                                        .bias_left(&snapshot);
1757
1758                                    editor.insert("\n", window, cx);
1759
1760                                    let crease_text = acp_thread::selection_name(
1761                                        Some(file_path.as_ref()),
1762                                        &line_range,
1763                                    );
1764
1765                                    let fold_placeholder = quote_selection_fold_placeholder(
1766                                        crease_text,
1767                                        weak_editor.clone(),
1768                                    );
1769                                    let crease = Crease::inline(
1770                                        anchor_before..anchor_after,
1771                                        fold_placeholder,
1772                                        render_quote_selection_output_toggle,
1773                                        |_, _, _, _| Empty.into_any(),
1774                                    );
1775                                    editor.insert_creases(vec![crease], cx);
1776                                    editor.fold_at(start_row, window, cx);
1777
1778                                    current_offset += selection.len;
1779                                    if !selection.is_entire_line && current_offset < text.len() {
1780                                        current_offset += 1;
1781                                    }
1782                                }
1783                            }
1784                        });
1785                        return;
1786                    }
1787                }
1788            }
1789        }
1790
1791        cx.stop_propagation();
1792
1793        let mut images = if let Some(item) = cx.read_from_clipboard() {
1794            item.into_entries()
1795                .filter_map(|entry| {
1796                    if let ClipboardEntry::Image(image) = entry {
1797                        Some(image)
1798                    } else {
1799                        None
1800                    }
1801                })
1802                .collect()
1803        } else {
1804            Vec::new()
1805        };
1806
1807        if let Some(paths) = cx.read_from_clipboard() {
1808            for path in paths
1809                .into_entries()
1810                .filter_map(|entry| {
1811                    if let ClipboardEntry::ExternalPaths(paths) = entry {
1812                        Some(paths.paths().to_owned())
1813                    } else {
1814                        None
1815                    }
1816                })
1817                .flatten()
1818            {
1819                let Ok(content) = std::fs::read(path) else {
1820                    continue;
1821                };
1822                let Ok(format) = image::guess_format(&content) else {
1823                    continue;
1824                };
1825                images.push(gpui::Image::from_bytes(
1826                    match format {
1827                        image::ImageFormat::Png => gpui::ImageFormat::Png,
1828                        image::ImageFormat::Jpeg => gpui::ImageFormat::Jpeg,
1829                        image::ImageFormat::WebP => gpui::ImageFormat::Webp,
1830                        image::ImageFormat::Gif => gpui::ImageFormat::Gif,
1831                        image::ImageFormat::Bmp => gpui::ImageFormat::Bmp,
1832                        image::ImageFormat::Tiff => gpui::ImageFormat::Tiff,
1833                        image::ImageFormat::Ico => gpui::ImageFormat::Ico,
1834                        _ => continue,
1835                    },
1836                    content,
1837                ));
1838            }
1839        }
1840
1841        let metadata = if let Some(item) = cx.read_from_clipboard() {
1842            item.entries().first().and_then(|entry| {
1843                if let ClipboardEntry::String(text) = entry {
1844                    text.metadata_json::<CopyMetadata>()
1845                } else {
1846                    None
1847                }
1848            })
1849        } else {
1850            None
1851        };
1852
1853        if images.is_empty() {
1854            self.editor.update(cx, |editor, cx| {
1855                let paste_position = editor
1856                    .selections
1857                    .newest::<MultiBufferOffset>(&editor.display_snapshot(cx))
1858                    .head();
1859                editor.paste(action, window, cx);
1860
1861                if let Some(metadata) = metadata {
1862                    let buffer = editor.buffer().read(cx).snapshot(cx);
1863
1864                    let mut buffer_rows_to_fold = BTreeSet::new();
1865                    let weak_editor = cx.entity().downgrade();
1866                    editor.insert_creases(
1867                        metadata.creases.into_iter().map(|metadata| {
1868                            let start = buffer.anchor_after(
1869                                paste_position + metadata.range_relative_to_selection.start,
1870                            );
1871                            let end = buffer.anchor_before(
1872                                paste_position + metadata.range_relative_to_selection.end,
1873                            );
1874
1875                            let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
1876                            buffer_rows_to_fold.insert(buffer_row);
1877                            Crease::inline(
1878                                start..end,
1879                                FoldPlaceholder {
1880                                    render: render_fold_icon_button(
1881                                        weak_editor.clone(),
1882                                        metadata.crease.icon_path.clone(),
1883                                        metadata.crease.label.clone(),
1884                                    ),
1885                                    ..Default::default()
1886                                },
1887                                render_slash_command_output_toggle,
1888                                |_, _, _, _| Empty.into_any(),
1889                            )
1890                            .with_metadata(metadata.crease)
1891                        }),
1892                        cx,
1893                    );
1894                    for buffer_row in buffer_rows_to_fold.into_iter().rev() {
1895                        editor.fold_at(buffer_row, window, cx);
1896                    }
1897                }
1898            });
1899        } else {
1900            let mut image_positions = Vec::new();
1901            self.editor.update(cx, |editor, cx| {
1902                editor.transact(window, cx, |editor, _window, cx| {
1903                    let edits = editor
1904                        .selections
1905                        .all::<MultiBufferOffset>(&editor.display_snapshot(cx))
1906                        .into_iter()
1907                        .map(|selection| (selection.start..selection.end, "\n"));
1908                    editor.edit(edits, cx);
1909
1910                    let snapshot = editor.buffer().read(cx).snapshot(cx);
1911                    for selection in editor
1912                        .selections
1913                        .all::<MultiBufferOffset>(&editor.display_snapshot(cx))
1914                    {
1915                        image_positions.push(snapshot.anchor_before(selection.end));
1916                    }
1917                });
1918            });
1919
1920            self.text_thread.update(cx, |text_thread, cx| {
1921                for image in images {
1922                    let Some(render_image) = image.to_image_data(cx.svg_renderer()).log_err()
1923                    else {
1924                        continue;
1925                    };
1926                    let image_id = image.id();
1927                    let image_task = LanguageModelImage::from_image(Arc::new(image), cx).shared();
1928
1929                    for image_position in image_positions.iter() {
1930                        text_thread.insert_content(
1931                            Content::Image {
1932                                anchor: image_position.text_anchor,
1933                                image_id,
1934                                image: image_task.clone(),
1935                                render_image: render_image.clone(),
1936                            },
1937                            cx,
1938                        );
1939                    }
1940                }
1941            });
1942        }
1943    }
1944
1945    fn update_image_blocks(&mut self, cx: &mut Context<Self>) {
1946        self.editor.update(cx, |editor, cx| {
1947            let buffer = editor.buffer().read(cx).snapshot(cx);
1948            let excerpt_id = *buffer.as_singleton().unwrap().0;
1949            let old_blocks = std::mem::take(&mut self.image_blocks);
1950            let new_blocks = self
1951                .text_thread
1952                .read(cx)
1953                .contents(cx)
1954                .map(
1955                    |Content::Image {
1956                         anchor,
1957                         render_image,
1958                         ..
1959                     }| (anchor, render_image),
1960                )
1961                .filter_map(|(anchor, render_image)| {
1962                    const MAX_HEIGHT_IN_LINES: u32 = 8;
1963                    let anchor = buffer.anchor_in_excerpt(excerpt_id, anchor).unwrap();
1964                    let image = render_image;
1965                    anchor.is_valid(&buffer).then(|| BlockProperties {
1966                        placement: BlockPlacement::Above(anchor),
1967                        height: Some(MAX_HEIGHT_IN_LINES),
1968                        style: BlockStyle::Sticky,
1969                        render: Arc::new(move |cx| {
1970                            let image_size = size_for_image(
1971                                &image,
1972                                size(
1973                                    cx.max_width - cx.margins.gutter.full_width(),
1974                                    MAX_HEIGHT_IN_LINES as f32 * cx.line_height,
1975                                ),
1976                            );
1977                            h_flex()
1978                                .pl(cx.margins.gutter.full_width())
1979                                .child(
1980                                    img(image.clone())
1981                                        .object_fit(gpui::ObjectFit::ScaleDown)
1982                                        .w(image_size.width)
1983                                        .h(image_size.height),
1984                                )
1985                                .into_any_element()
1986                        }),
1987                        priority: 0,
1988                    })
1989                })
1990                .collect::<Vec<_>>();
1991
1992            editor.remove_blocks(old_blocks, None, cx);
1993            let ids = editor.insert_blocks(new_blocks, None, cx);
1994            self.image_blocks = HashSet::from_iter(ids);
1995        });
1996    }
1997
1998    fn split(&mut self, _: &Split, _window: &mut Window, cx: &mut Context<Self>) {
1999        self.text_thread.update(cx, |text_thread, cx| {
2000            let selections = self.editor.read(cx).selections.disjoint_anchors_arc();
2001            for selection in selections.as_ref() {
2002                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
2003                let range = selection
2004                    .map(|endpoint| endpoint.to_offset(&buffer))
2005                    .range();
2006                text_thread.split_message(range.start.0..range.end.0, cx);
2007            }
2008        });
2009    }
2010
2011    fn save(&mut self, _: &Save, _window: &mut Window, cx: &mut Context<Self>) {
2012        self.text_thread.update(cx, |text_thread, cx| {
2013            text_thread.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
2014        });
2015    }
2016
2017    pub fn title(&self, cx: &App) -> SharedString {
2018        self.text_thread.read(cx).summary().or_default()
2019    }
2020
2021    pub fn regenerate_summary(&mut self, cx: &mut Context<Self>) {
2022        self.text_thread
2023            .update(cx, |text_thread, cx| text_thread.summarize(true, cx));
2024    }
2025
2026    fn render_remaining_tokens(&self, cx: &App) -> Option<impl IntoElement + use<>> {
2027        let (token_count_color, token_count, max_token_count, tooltip) =
2028            match token_state(&self.text_thread, cx)? {
2029                TokenState::NoTokensLeft {
2030                    max_token_count,
2031                    token_count,
2032                } => (
2033                    Color::Error,
2034                    token_count,
2035                    max_token_count,
2036                    Some("Token Limit Reached"),
2037                ),
2038                TokenState::HasMoreTokens {
2039                    max_token_count,
2040                    token_count,
2041                    over_warn_threshold,
2042                } => {
2043                    let (color, tooltip) = if over_warn_threshold {
2044                        (Color::Warning, Some("Token Limit is Close to Exhaustion"))
2045                    } else {
2046                        (Color::Muted, None)
2047                    };
2048                    (color, token_count, max_token_count, tooltip)
2049                }
2050            };
2051
2052        Some(
2053            h_flex()
2054                .id("token-count")
2055                .gap_0p5()
2056                .child(
2057                    Label::new(humanize_token_count(token_count))
2058                        .size(LabelSize::Small)
2059                        .color(token_count_color),
2060                )
2061                .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
2062                .child(
2063                    Label::new(humanize_token_count(max_token_count))
2064                        .size(LabelSize::Small)
2065                        .color(Color::Muted),
2066                )
2067                .when_some(tooltip, |element, tooltip| {
2068                    element.tooltip(Tooltip::text(tooltip))
2069                }),
2070        )
2071    }
2072
2073    fn render_send_button(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2074        let focus_handle = self.focus_handle(cx);
2075
2076        let (style, tooltip) = match token_state(&self.text_thread, cx) {
2077            Some(TokenState::NoTokensLeft { .. }) => (
2078                ButtonStyle::Tinted(TintColor::Error),
2079                Some(Tooltip::text("Token limit reached")(window, cx)),
2080            ),
2081            Some(TokenState::HasMoreTokens {
2082                over_warn_threshold,
2083                ..
2084            }) => {
2085                let (style, tooltip) = if over_warn_threshold {
2086                    (
2087                        ButtonStyle::Tinted(TintColor::Warning),
2088                        Some(Tooltip::text("Token limit is close to exhaustion")(
2089                            window, cx,
2090                        )),
2091                    )
2092                } else {
2093                    (ButtonStyle::Filled, None)
2094                };
2095                (style, tooltip)
2096            }
2097            None => (ButtonStyle::Filled, None),
2098        };
2099
2100        Button::new("send_button", "Send")
2101            .label_size(LabelSize::Small)
2102            .disabled(self.sending_disabled(cx))
2103            .style(style)
2104            .when_some(tooltip, |button, tooltip| {
2105                button.tooltip(move |_, _| tooltip.clone())
2106            })
2107            .layer(ElevationIndex::ModalSurface)
2108            .key_binding(
2109                KeyBinding::for_action_in(&Assist, &focus_handle, cx)
2110                    .map(|kb| kb.size(rems_from_px(12.))),
2111            )
2112            .on_click(move |_event, window, cx| {
2113                focus_handle.dispatch_action(&Assist, window, cx);
2114            })
2115    }
2116
2117    /// Whether or not we should allow messages to be sent.
2118    /// Will return false if the selected provided has a configuration error or
2119    /// if the user has not accepted the terms of service for this provider.
2120    fn sending_disabled(&self, cx: &mut Context<'_, TextThreadEditor>) -> bool {
2121        let model_registry = LanguageModelRegistry::read_global(cx);
2122        let Some(configuration_error) =
2123            model_registry.configuration_error(model_registry.default_model(), cx)
2124        else {
2125            return false;
2126        };
2127
2128        match configuration_error {
2129            ConfigurationError::NoProvider
2130            | ConfigurationError::ModelNotFound
2131            | ConfigurationError::ProviderNotAuthenticated(_) => true,
2132        }
2133    }
2134
2135    fn render_inject_context_menu(&self, cx: &mut Context<Self>) -> impl IntoElement {
2136        slash_command_picker::SlashCommandSelector::new(
2137            self.slash_commands.clone(),
2138            cx.entity().downgrade(),
2139            IconButton::new("trigger", IconName::Plus)
2140                .icon_size(IconSize::Small)
2141                .icon_color(Color::Muted)
2142                .selected_icon_color(Color::Accent)
2143                .selected_style(ButtonStyle::Filled),
2144            move |_window, cx| {
2145                Tooltip::with_meta("Add Context", None, "Type / to insert via keyboard", cx)
2146            },
2147        )
2148    }
2149
2150    fn render_burn_mode_toggle(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
2151        let text_thread = self.text_thread().read(cx);
2152        let active_model = LanguageModelRegistry::read_global(cx)
2153            .default_model()
2154            .map(|default| default.model)?;
2155        if !active_model.supports_burn_mode() {
2156            return None;
2157        }
2158
2159        let active_completion_mode = text_thread.completion_mode();
2160        let burn_mode_enabled = active_completion_mode == CompletionMode::Burn;
2161        let icon = if burn_mode_enabled {
2162            IconName::ZedBurnModeOn
2163        } else {
2164            IconName::ZedBurnMode
2165        };
2166
2167        Some(
2168            IconButton::new("burn-mode", icon)
2169                .icon_size(IconSize::Small)
2170                .icon_color(Color::Muted)
2171                .toggle_state(burn_mode_enabled)
2172                .selected_icon_color(Color::Error)
2173                .on_click(cx.listener(move |this, _event, _window, cx| {
2174                    this.text_thread().update(cx, |text_thread, _cx| {
2175                        text_thread.set_completion_mode(match active_completion_mode {
2176                            CompletionMode::Burn => CompletionMode::Normal,
2177                            CompletionMode::Normal => CompletionMode::Burn,
2178                        });
2179                    });
2180                }))
2181                .tooltip(move |_window, cx| {
2182                    cx.new(|_| BurnModeTooltip::new().selected(burn_mode_enabled))
2183                        .into()
2184                })
2185                .into_any_element(),
2186        )
2187    }
2188
2189    fn render_language_model_selector(
2190        &self,
2191        window: &mut Window,
2192        cx: &mut Context<Self>,
2193    ) -> impl IntoElement {
2194        let active_model = LanguageModelRegistry::read_global(cx)
2195            .default_model()
2196            .map(|default| default.model);
2197        let model_name = match active_model {
2198            Some(model) => model.name().0,
2199            None => SharedString::from("Select Model"),
2200        };
2201
2202        let active_provider = LanguageModelRegistry::read_global(cx)
2203            .default_model()
2204            .map(|default| default.provider);
2205
2206        let provider_icon = match active_provider {
2207            Some(provider) => provider.icon(),
2208            None => IconName::Ai,
2209        };
2210
2211        let focus_handle = self.editor().focus_handle(cx);
2212        let (color, icon) = if self.language_model_selector_menu_handle.is_deployed() {
2213            (Color::Accent, IconName::ChevronUp)
2214        } else {
2215            (Color::Muted, IconName::ChevronDown)
2216        };
2217
2218        PickerPopoverMenu::new(
2219            self.language_model_selector.clone(),
2220            ButtonLike::new("active-model")
2221                .selected_style(ButtonStyle::Tinted(TintColor::Accent))
2222                .child(
2223                    h_flex()
2224                        .gap_0p5()
2225                        .child(Icon::new(provider_icon).color(color).size(IconSize::XSmall))
2226                        .child(
2227                            Label::new(model_name)
2228                                .color(color)
2229                                .size(LabelSize::Small)
2230                                .ml_0p5(),
2231                        )
2232                        .child(Icon::new(icon).color(color).size(IconSize::XSmall)),
2233                ),
2234            move |_window, cx| {
2235                Tooltip::for_action_in("Change Model", &ToggleModelSelector, &focus_handle, cx)
2236            },
2237            gpui::Corner::BottomRight,
2238            cx,
2239        )
2240        .with_handle(self.language_model_selector_menu_handle.clone())
2241        .render(window, cx)
2242    }
2243
2244    fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
2245        let last_error = self.last_error.as_ref()?;
2246
2247        Some(
2248            div()
2249                .absolute()
2250                .right_3()
2251                .bottom_12()
2252                .max_w_96()
2253                .py_2()
2254                .px_3()
2255                .elevation_2(cx)
2256                .occlude()
2257                .child(match last_error {
2258                    AssistError::PaymentRequired => self.render_payment_required_error(cx),
2259                    AssistError::Message(error_message) => {
2260                        self.render_assist_error(error_message, cx)
2261                    }
2262                })
2263                .into_any(),
2264        )
2265    }
2266
2267    fn render_payment_required_error(&self, cx: &mut Context<Self>) -> AnyElement {
2268        const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
2269
2270        v_flex()
2271            .gap_0p5()
2272            .child(
2273                h_flex()
2274                    .gap_1p5()
2275                    .items_center()
2276                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2277                    .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
2278            )
2279            .child(
2280                div()
2281                    .id("error-message")
2282                    .max_h_24()
2283                    .overflow_y_scroll()
2284                    .child(Label::new(ERROR_MESSAGE)),
2285            )
2286            .child(
2287                h_flex()
2288                    .justify_end()
2289                    .mt_1()
2290                    .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
2291                        |this, _, _window, cx| {
2292                            this.last_error = None;
2293                            cx.open_url(&zed_urls::account_url(cx));
2294                            cx.notify();
2295                        },
2296                    )))
2297                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2298                        |this, _, _window, cx| {
2299                            this.last_error = None;
2300                            cx.notify();
2301                        },
2302                    ))),
2303            )
2304            .into_any()
2305    }
2306
2307    fn render_assist_error(
2308        &self,
2309        error_message: &SharedString,
2310        cx: &mut Context<Self>,
2311    ) -> AnyElement {
2312        v_flex()
2313            .gap_0p5()
2314            .child(
2315                h_flex()
2316                    .gap_1p5()
2317                    .items_center()
2318                    .child(Icon::new(IconName::XCircle).color(Color::Error))
2319                    .child(
2320                        Label::new("Error interacting with language model")
2321                            .weight(FontWeight::MEDIUM),
2322                    ),
2323            )
2324            .child(
2325                div()
2326                    .id("error-message")
2327                    .max_h_32()
2328                    .overflow_y_scroll()
2329                    .child(Label::new(error_message.clone())),
2330            )
2331            .child(
2332                h_flex()
2333                    .justify_end()
2334                    .mt_1()
2335                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
2336                        |this, _, _window, cx| {
2337                            this.last_error = None;
2338                            cx.notify();
2339                        },
2340                    ))),
2341            )
2342            .into_any()
2343    }
2344}
2345
2346/// Returns the contents of the *outermost* fenced code block that contains the given offset.
2347fn find_surrounding_code_block(snapshot: &BufferSnapshot, offset: usize) -> Option<Range<usize>> {
2348    const CODE_BLOCK_NODE: &str = "fenced_code_block";
2349    const CODE_BLOCK_CONTENT: &str = "code_fence_content";
2350
2351    let layer = snapshot.syntax_layers().next()?;
2352
2353    let root_node = layer.node();
2354    let mut cursor = root_node.walk();
2355
2356    // Go to the first child for the given offset
2357    while cursor.goto_first_child_for_byte(offset).is_some() {
2358        // If we're at the end of the node, go to the next one.
2359        // Example: if you have a fenced-code-block, and you're on the start of the line
2360        // right after the closing ```, you want to skip the fenced-code-block and
2361        // go to the next sibling.
2362        if cursor.node().end_byte() == offset {
2363            cursor.goto_next_sibling();
2364        }
2365
2366        if cursor.node().start_byte() > offset {
2367            break;
2368        }
2369
2370        // We found the fenced code block.
2371        if cursor.node().kind() == CODE_BLOCK_NODE {
2372            // Now we need to find the child node that contains the code.
2373            cursor.goto_first_child();
2374            loop {
2375                if cursor.node().kind() == CODE_BLOCK_CONTENT {
2376                    return Some(cursor.node().byte_range());
2377                }
2378                if !cursor.goto_next_sibling() {
2379                    break;
2380                }
2381            }
2382        }
2383    }
2384
2385    None
2386}
2387
2388fn render_thought_process_fold_icon_button(
2389    editor: WeakEntity<Editor>,
2390    status: ThoughtProcessStatus,
2391) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement> {
2392    Arc::new(move |fold_id, fold_range, _cx| {
2393        let editor = editor.clone();
2394
2395        let button = ButtonLike::new(fold_id).layer(ElevationIndex::ElevatedSurface);
2396        let button = match status {
2397            ThoughtProcessStatus::Pending => button
2398                .child(
2399                    Icon::new(IconName::ToolThink)
2400                        .size(IconSize::Small)
2401                        .color(Color::Muted),
2402                )
2403                .child(
2404                    Label::new("Thinking…").color(Color::Muted).with_animation(
2405                        "pulsating-label",
2406                        Animation::new(Duration::from_secs(2))
2407                            .repeat()
2408                            .with_easing(pulsating_between(0.4, 0.8)),
2409                        |label, delta| label.alpha(delta),
2410                    ),
2411                ),
2412            ThoughtProcessStatus::Completed => button
2413                .style(ButtonStyle::Filled)
2414                .child(Icon::new(IconName::ToolThink).size(IconSize::Small))
2415                .child(Label::new("Thought Process").single_line()),
2416        };
2417
2418        button
2419            .on_click(move |_, window, cx| {
2420                editor
2421                    .update(cx, |editor, cx| {
2422                        let buffer_start = fold_range
2423                            .start
2424                            .to_point(&editor.buffer().read(cx).read(cx));
2425                        let buffer_row = MultiBufferRow(buffer_start.row);
2426                        editor.unfold_at(buffer_row, window, cx);
2427                    })
2428                    .ok();
2429            })
2430            .into_any_element()
2431    })
2432}
2433
2434fn render_fold_icon_button(
2435    editor: WeakEntity<Editor>,
2436    icon_path: SharedString,
2437    label: SharedString,
2438) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut App) -> AnyElement> {
2439    Arc::new(move |fold_id, fold_range, _cx| {
2440        let editor = editor.clone();
2441        ButtonLike::new(fold_id)
2442            .style(ButtonStyle::Filled)
2443            .layer(ElevationIndex::ElevatedSurface)
2444            .child(Icon::from_path(icon_path.clone()))
2445            .child(Label::new(label.clone()).single_line())
2446            .on_click(move |_, window, cx| {
2447                editor
2448                    .update(cx, |editor, cx| {
2449                        let buffer_start = fold_range
2450                            .start
2451                            .to_point(&editor.buffer().read(cx).read(cx));
2452                        let buffer_row = MultiBufferRow(buffer_start.row);
2453                        editor.unfold_at(buffer_row, window, cx);
2454                    })
2455                    .ok();
2456            })
2457            .into_any_element()
2458    })
2459}
2460
2461type ToggleFold = Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>;
2462
2463fn render_slash_command_output_toggle(
2464    row: MultiBufferRow,
2465    is_folded: bool,
2466    fold: ToggleFold,
2467    _window: &mut Window,
2468    _cx: &mut App,
2469) -> AnyElement {
2470    Disclosure::new(
2471        ("slash-command-output-fold-indicator", row.0 as u64),
2472        !is_folded,
2473    )
2474    .toggle_state(is_folded)
2475    .on_click(move |_e, window, cx| fold(!is_folded, window, cx))
2476    .into_any_element()
2477}
2478
2479pub fn fold_toggle(
2480    name: &'static str,
2481) -> impl Fn(
2482    MultiBufferRow,
2483    bool,
2484    Arc<dyn Fn(bool, &mut Window, &mut App) + Send + Sync>,
2485    &mut Window,
2486    &mut App,
2487) -> AnyElement {
2488    move |row, is_folded, fold, _window, _cx| {
2489        Disclosure::new((name, row.0 as u64), !is_folded)
2490            .toggle_state(is_folded)
2491            .on_click(move |_e, window, cx| fold(!is_folded, window, cx))
2492            .into_any_element()
2493    }
2494}
2495
2496fn quote_selection_fold_placeholder(title: String, editor: WeakEntity<Editor>) -> FoldPlaceholder {
2497    FoldPlaceholder {
2498        render: Arc::new({
2499            move |fold_id, fold_range, _cx| {
2500                let editor = editor.clone();
2501                ButtonLike::new(fold_id)
2502                    .style(ButtonStyle::Filled)
2503                    .layer(ElevationIndex::ElevatedSurface)
2504                    .child(Icon::new(IconName::TextSnippet))
2505                    .child(Label::new(title.clone()).single_line())
2506                    .on_click(move |_, window, cx| {
2507                        editor
2508                            .update(cx, |editor, cx| {
2509                                let buffer_start = fold_range
2510                                    .start
2511                                    .to_point(&editor.buffer().read(cx).read(cx));
2512                                let buffer_row = MultiBufferRow(buffer_start.row);
2513                                editor.unfold_at(buffer_row, window, cx);
2514                            })
2515                            .ok();
2516                    })
2517                    .into_any_element()
2518            }
2519        }),
2520        merge_adjacent: false,
2521        ..Default::default()
2522    }
2523}
2524
2525fn render_quote_selection_output_toggle(
2526    row: MultiBufferRow,
2527    is_folded: bool,
2528    fold: ToggleFold,
2529    _window: &mut Window,
2530    _cx: &mut App,
2531) -> AnyElement {
2532    Disclosure::new(("quote-selection-indicator", row.0 as u64), !is_folded)
2533        .toggle_state(is_folded)
2534        .on_click(move |_e, window, cx| fold(!is_folded, window, cx))
2535        .into_any_element()
2536}
2537
2538fn render_pending_slash_command_gutter_decoration(
2539    row: MultiBufferRow,
2540    status: &PendingSlashCommandStatus,
2541    confirm_command: Arc<dyn Fn(&mut Window, &mut App)>,
2542) -> AnyElement {
2543    let mut icon = IconButton::new(
2544        ("slash-command-gutter-decoration", row.0),
2545        ui::IconName::TriangleRight,
2546    )
2547    .on_click(move |_e, window, cx| confirm_command(window, cx))
2548    .icon_size(ui::IconSize::Small)
2549    .size(ui::ButtonSize::None);
2550
2551    match status {
2552        PendingSlashCommandStatus::Idle => {
2553            icon = icon.icon_color(Color::Muted);
2554        }
2555        PendingSlashCommandStatus::Running { .. } => {
2556            icon = icon.toggle_state(true);
2557        }
2558        PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
2559    }
2560
2561    icon.into_any_element()
2562}
2563
2564#[derive(Debug, Clone, Serialize, Deserialize)]
2565struct CopyMetadata {
2566    creases: Vec<SelectedCreaseMetadata>,
2567}
2568
2569#[derive(Debug, Clone, Serialize, Deserialize)]
2570struct SelectedCreaseMetadata {
2571    range_relative_to_selection: Range<usize>,
2572    crease: CreaseMetadata,
2573}
2574
2575impl EventEmitter<EditorEvent> for TextThreadEditor {}
2576impl EventEmitter<SearchEvent> for TextThreadEditor {}
2577
2578impl Render for TextThreadEditor {
2579    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
2580        let language_model_selector = self.language_model_selector_menu_handle.clone();
2581
2582        v_flex()
2583            .key_context("ContextEditor")
2584            .capture_action(cx.listener(TextThreadEditor::cancel))
2585            .capture_action(cx.listener(TextThreadEditor::save))
2586            .capture_action(cx.listener(TextThreadEditor::copy))
2587            .capture_action(cx.listener(TextThreadEditor::cut))
2588            .capture_action(cx.listener(TextThreadEditor::paste))
2589            .capture_action(cx.listener(TextThreadEditor::cycle_message_role))
2590            .capture_action(cx.listener(TextThreadEditor::confirm_command))
2591            .on_action(cx.listener(TextThreadEditor::assist))
2592            .on_action(cx.listener(TextThreadEditor::split))
2593            .on_action(move |_: &ToggleModelSelector, window, cx| {
2594                language_model_selector.toggle(window, cx);
2595            })
2596            .size_full()
2597            .child(
2598                div()
2599                    .flex_grow()
2600                    .bg(cx.theme().colors().editor_background)
2601                    .child(self.editor.clone()),
2602            )
2603            .children(self.render_last_error(cx))
2604            .child(
2605                h_flex()
2606                    .relative()
2607                    .py_2()
2608                    .pl_1p5()
2609                    .pr_2()
2610                    .w_full()
2611                    .justify_between()
2612                    .border_t_1()
2613                    .border_color(cx.theme().colors().border_variant)
2614                    .bg(cx.theme().colors().editor_background)
2615                    .child(
2616                        h_flex()
2617                            .gap_0p5()
2618                            .child(self.render_inject_context_menu(cx))
2619                            .children(self.render_burn_mode_toggle(cx)),
2620                    )
2621                    .child(
2622                        h_flex()
2623                            .gap_2p5()
2624                            .children(self.render_remaining_tokens(cx))
2625                            .child(
2626                                h_flex()
2627                                    .gap_1()
2628                                    .child(self.render_language_model_selector(window, cx))
2629                                    .child(self.render_send_button(window, cx)),
2630                            ),
2631                    ),
2632            )
2633    }
2634}
2635
2636impl Focusable for TextThreadEditor {
2637    fn focus_handle(&self, cx: &App) -> FocusHandle {
2638        self.editor.focus_handle(cx)
2639    }
2640}
2641
2642impl Item for TextThreadEditor {
2643    type Event = editor::EditorEvent;
2644
2645    fn tab_content_text(&self, _detail: usize, cx: &App) -> SharedString {
2646        util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into()
2647    }
2648
2649    fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
2650        match event {
2651            EditorEvent::Edited { .. } => {
2652                f(item::ItemEvent::Edit);
2653            }
2654            EditorEvent::TitleChanged => {
2655                f(item::ItemEvent::UpdateTab);
2656            }
2657            _ => {}
2658        }
2659    }
2660
2661    fn tab_tooltip_text(&self, cx: &App) -> Option<SharedString> {
2662        Some(self.title(cx).to_string().into())
2663    }
2664
2665    fn as_searchable(
2666        &self,
2667        handle: &Entity<Self>,
2668        _: &App,
2669    ) -> Option<Box<dyn SearchableItemHandle>> {
2670        Some(Box::new(handle.clone()))
2671    }
2672
2673    fn set_nav_history(
2674        &mut self,
2675        nav_history: pane::ItemNavHistory,
2676        window: &mut Window,
2677        cx: &mut Context<Self>,
2678    ) {
2679        self.editor.update(cx, |editor, cx| {
2680            Item::set_nav_history(editor, nav_history, window, cx)
2681        })
2682    }
2683
2684    fn navigate(
2685        &mut self,
2686        data: Box<dyn std::any::Any>,
2687        window: &mut Window,
2688        cx: &mut Context<Self>,
2689    ) -> bool {
2690        self.editor
2691            .update(cx, |editor, cx| Item::navigate(editor, data, window, cx))
2692    }
2693
2694    fn deactivated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2695        self.editor
2696            .update(cx, |editor, cx| Item::deactivated(editor, window, cx))
2697    }
2698
2699    fn act_as_type<'a>(
2700        &'a self,
2701        type_id: TypeId,
2702        self_handle: &'a Entity<Self>,
2703        _: &'a App,
2704    ) -> Option<gpui::AnyEntity> {
2705        if type_id == TypeId::of::<Self>() {
2706            Some(self_handle.clone().into())
2707        } else if type_id == TypeId::of::<Editor>() {
2708            Some(self.editor.clone().into())
2709        } else {
2710            None
2711        }
2712    }
2713
2714    fn include_in_nav_history() -> bool {
2715        false
2716    }
2717}
2718
2719impl SearchableItem for TextThreadEditor {
2720    type Match = <Editor as SearchableItem>::Match;
2721
2722    fn clear_matches(&mut self, window: &mut Window, cx: &mut Context<Self>) {
2723        self.editor.update(cx, |editor, cx| {
2724            editor.clear_matches(window, cx);
2725        });
2726    }
2727
2728    fn update_matches(
2729        &mut self,
2730        matches: &[Self::Match],
2731        active_match_index: Option<usize>,
2732        window: &mut Window,
2733        cx: &mut Context<Self>,
2734    ) {
2735        self.editor.update(cx, |editor, cx| {
2736            editor.update_matches(matches, active_match_index, window, cx)
2737        });
2738    }
2739
2740    fn query_suggestion(&mut self, window: &mut Window, cx: &mut Context<Self>) -> String {
2741        self.editor
2742            .update(cx, |editor, cx| editor.query_suggestion(window, cx))
2743    }
2744
2745    fn activate_match(
2746        &mut self,
2747        index: usize,
2748        matches: &[Self::Match],
2749        window: &mut Window,
2750        cx: &mut Context<Self>,
2751    ) {
2752        self.editor.update(cx, |editor, cx| {
2753            editor.activate_match(index, matches, window, cx);
2754        });
2755    }
2756
2757    fn select_matches(
2758        &mut self,
2759        matches: &[Self::Match],
2760        window: &mut Window,
2761        cx: &mut Context<Self>,
2762    ) {
2763        self.editor
2764            .update(cx, |editor, cx| editor.select_matches(matches, window, cx));
2765    }
2766
2767    fn replace(
2768        &mut self,
2769        identifier: &Self::Match,
2770        query: &project::search::SearchQuery,
2771        window: &mut Window,
2772        cx: &mut Context<Self>,
2773    ) {
2774        self.editor.update(cx, |editor, cx| {
2775            editor.replace(identifier, query, window, cx)
2776        });
2777    }
2778
2779    fn find_matches(
2780        &mut self,
2781        query: Arc<project::search::SearchQuery>,
2782        window: &mut Window,
2783        cx: &mut Context<Self>,
2784    ) -> Task<Vec<Self::Match>> {
2785        self.editor
2786            .update(cx, |editor, cx| editor.find_matches(query, window, cx))
2787    }
2788
2789    fn active_match_index(
2790        &mut self,
2791        direction: Direction,
2792        matches: &[Self::Match],
2793        window: &mut Window,
2794        cx: &mut Context<Self>,
2795    ) -> Option<usize> {
2796        self.editor.update(cx, |editor, cx| {
2797            editor.active_match_index(direction, matches, window, cx)
2798        })
2799    }
2800}
2801
2802impl FollowableItem for TextThreadEditor {
2803    fn remote_id(&self) -> Option<workspace::ViewId> {
2804        self.remote_id
2805    }
2806
2807    fn to_state_proto(&self, window: &Window, cx: &App) -> Option<proto::view::Variant> {
2808        let text_thread = self.text_thread.read(cx);
2809        Some(proto::view::Variant::ContextEditor(
2810            proto::view::ContextEditor {
2811                context_id: text_thread.id().to_proto(),
2812                editor: if let Some(proto::view::Variant::Editor(proto)) =
2813                    self.editor.read(cx).to_state_proto(window, cx)
2814                {
2815                    Some(proto)
2816                } else {
2817                    None
2818                },
2819            },
2820        ))
2821    }
2822
2823    fn from_state_proto(
2824        workspace: Entity<Workspace>,
2825        id: workspace::ViewId,
2826        state: &mut Option<proto::view::Variant>,
2827        window: &mut Window,
2828        cx: &mut App,
2829    ) -> Option<Task<Result<Entity<Self>>>> {
2830        let proto::view::Variant::ContextEditor(_) = state.as_ref()? else {
2831            return None;
2832        };
2833        let Some(proto::view::Variant::ContextEditor(state)) = state.take() else {
2834            unreachable!()
2835        };
2836
2837        let text_thread_id = TextThreadId::from_proto(state.context_id);
2838        let editor_state = state.editor?;
2839
2840        let project = workspace.read(cx).project().clone();
2841        let agent_panel_delegate = <dyn AgentPanelDelegate>::try_global(cx)?;
2842
2843        let text_thread_editor_task = workspace.update(cx, |workspace, cx| {
2844            agent_panel_delegate.open_remote_text_thread(workspace, text_thread_id, window, cx)
2845        });
2846
2847        Some(window.spawn(cx, async move |cx| {
2848            let text_thread_editor = text_thread_editor_task.await?;
2849            text_thread_editor
2850                .update_in(cx, |text_thread_editor, window, cx| {
2851                    text_thread_editor.remote_id = Some(id);
2852                    text_thread_editor.editor.update(cx, |editor, cx| {
2853                        editor.apply_update_proto(
2854                            &project,
2855                            proto::update_view::Variant::Editor(proto::update_view::Editor {
2856                                selections: editor_state.selections,
2857                                pending_selection: editor_state.pending_selection,
2858                                scroll_top_anchor: editor_state.scroll_top_anchor,
2859                                scroll_x: editor_state.scroll_y,
2860                                scroll_y: editor_state.scroll_y,
2861                                ..Default::default()
2862                            }),
2863                            window,
2864                            cx,
2865                        )
2866                    })
2867                })?
2868                .await?;
2869            Ok(text_thread_editor)
2870        }))
2871    }
2872
2873    fn to_follow_event(event: &Self::Event) -> Option<item::FollowEvent> {
2874        Editor::to_follow_event(event)
2875    }
2876
2877    fn add_event_to_update_proto(
2878        &self,
2879        event: &Self::Event,
2880        update: &mut Option<proto::update_view::Variant>,
2881        window: &Window,
2882        cx: &App,
2883    ) -> bool {
2884        self.editor
2885            .read(cx)
2886            .add_event_to_update_proto(event, update, window, cx)
2887    }
2888
2889    fn apply_update_proto(
2890        &mut self,
2891        project: &Entity<Project>,
2892        message: proto::update_view::Variant,
2893        window: &mut Window,
2894        cx: &mut Context<Self>,
2895    ) -> Task<Result<()>> {
2896        self.editor.update(cx, |editor, cx| {
2897            editor.apply_update_proto(project, message, window, cx)
2898        })
2899    }
2900
2901    fn is_project_item(&self, _window: &Window, _cx: &App) -> bool {
2902        true
2903    }
2904
2905    fn set_leader_id(
2906        &mut self,
2907        leader_id: Option<CollaboratorId>,
2908        window: &mut Window,
2909        cx: &mut Context<Self>,
2910    ) {
2911        self.editor
2912            .update(cx, |editor, cx| editor.set_leader_id(leader_id, window, cx))
2913    }
2914
2915    fn dedup(&self, existing: &Self, _window: &Window, cx: &App) -> Option<item::Dedup> {
2916        if existing.text_thread.read(cx).id() == self.text_thread.read(cx).id() {
2917            Some(item::Dedup::KeepExisting)
2918        } else {
2919            None
2920        }
2921    }
2922}
2923
2924enum PendingSlashCommand {}
2925
2926fn invoked_slash_command_fold_placeholder(
2927    command_id: InvokedSlashCommandId,
2928    text_thread: WeakEntity<TextThread>,
2929) -> FoldPlaceholder {
2930    FoldPlaceholder {
2931        constrain_width: false,
2932        merge_adjacent: false,
2933        render: Arc::new(move |fold_id, _, cx| {
2934            let Some(text_thread) = text_thread.upgrade() else {
2935                return Empty.into_any();
2936            };
2937
2938            let Some(command) = text_thread.read(cx).invoked_slash_command(&command_id) else {
2939                return Empty.into_any();
2940            };
2941
2942            h_flex()
2943                .id(fold_id)
2944                .px_1()
2945                .ml_6()
2946                .gap_2()
2947                .bg(cx.theme().colors().surface_background)
2948                .rounded_sm()
2949                .child(Label::new(format!("/{}", command.name)))
2950                .map(|parent| match &command.status {
2951                    InvokedSlashCommandStatus::Running(_) => {
2952                        parent.child(Icon::new(IconName::ArrowCircle).with_rotate_animation(4))
2953                    }
2954                    InvokedSlashCommandStatus::Error(message) => parent.child(
2955                        Label::new(format!("error: {message}"))
2956                            .single_line()
2957                            .color(Color::Error),
2958                    ),
2959                    InvokedSlashCommandStatus::Finished => parent,
2960                })
2961                .into_any_element()
2962        }),
2963        type_tag: Some(TypeId::of::<PendingSlashCommand>()),
2964    }
2965}
2966
2967enum TokenState {
2968    NoTokensLeft {
2969        max_token_count: u64,
2970        token_count: u64,
2971    },
2972    HasMoreTokens {
2973        max_token_count: u64,
2974        token_count: u64,
2975        over_warn_threshold: bool,
2976    },
2977}
2978
2979fn token_state(text_thread: &Entity<TextThread>, cx: &App) -> Option<TokenState> {
2980    const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
2981
2982    let model = LanguageModelRegistry::read_global(cx)
2983        .default_model()?
2984        .model;
2985    let token_count = text_thread.read(cx).token_count()?;
2986    let max_token_count =
2987        model.max_token_count_for_mode(text_thread.read(cx).completion_mode().into());
2988    let token_state = if max_token_count.saturating_sub(token_count) == 0 {
2989        TokenState::NoTokensLeft {
2990            max_token_count,
2991            token_count,
2992        }
2993    } else {
2994        let over_warn_threshold =
2995            token_count as f32 / max_token_count as f32 >= WARNING_TOKEN_THRESHOLD;
2996        TokenState::HasMoreTokens {
2997            max_token_count,
2998            token_count,
2999            over_warn_threshold,
3000        }
3001    };
3002    Some(token_state)
3003}
3004
3005fn size_for_image(data: &RenderImage, max_size: Size<Pixels>) -> Size<Pixels> {
3006    let image_size = data
3007        .size(0)
3008        .map(|dimension| Pixels::from(u32::from(dimension)));
3009    let image_ratio = image_size.width / image_size.height;
3010    let bounds_ratio = max_size.width / max_size.height;
3011
3012    if image_size.width > max_size.width || image_size.height > max_size.height {
3013        if bounds_ratio > image_ratio {
3014            size(
3015                image_size.width * (max_size.height / image_size.height),
3016                max_size.height,
3017            )
3018        } else {
3019            size(
3020                max_size.width,
3021                image_size.height * (max_size.width / image_size.width),
3022            )
3023        }
3024    } else {
3025        size(image_size.width, image_size.height)
3026    }
3027}
3028
3029pub fn humanize_token_count(count: u64) -> String {
3030    match count {
3031        0..=999 => count.to_string(),
3032        1000..=9999 => {
3033            let thousands = count / 1000;
3034            let hundreds = (count % 1000 + 50) / 100;
3035            if hundreds == 0 {
3036                format!("{}k", thousands)
3037            } else if hundreds == 10 {
3038                format!("{}k", thousands + 1)
3039            } else {
3040                format!("{}.{}k", thousands, hundreds)
3041            }
3042        }
3043        1_000_000..=9_999_999 => {
3044            let millions = count / 1_000_000;
3045            let hundred_thousands = (count % 1_000_000 + 50_000) / 100_000;
3046            if hundred_thousands == 0 {
3047                format!("{}M", millions)
3048            } else if hundred_thousands == 10 {
3049                format!("{}M", millions + 1)
3050            } else {
3051                format!("{}.{}M", millions, hundred_thousands)
3052            }
3053        }
3054        10_000_000.. => format!("{}M", (count + 500_000) / 1_000_000),
3055        _ => format!("{}k", (count + 500) / 1000),
3056    }
3057}
3058
3059pub fn make_lsp_adapter_delegate(
3060    project: &Entity<Project>,
3061    cx: &mut App,
3062) -> Result<Option<Arc<dyn LspAdapterDelegate>>> {
3063    project.update(cx, |project, cx| {
3064        // TODO: Find the right worktree.
3065        let Some(worktree) = project.worktrees(cx).next() else {
3066            return Ok(None::<Arc<dyn LspAdapterDelegate>>);
3067        };
3068        let http_client = project.client().http_client();
3069        project.lsp_store().update(cx, |_, cx| {
3070            Ok(Some(LocalLspAdapterDelegate::new(
3071                project.languages().clone(),
3072                project.environment(),
3073                cx.weak_entity(),
3074                &worktree,
3075                http_client,
3076                project.fs().clone(),
3077                cx,
3078            ) as Arc<dyn LspAdapterDelegate>))
3079        })
3080    })
3081}
3082
3083#[cfg(test)]
3084mod tests {
3085    use super::*;
3086    use editor::{MultiBufferOffset, SelectionEffects};
3087    use fs::FakeFs;
3088    use gpui::{App, TestAppContext, VisualTestContext};
3089    use indoc::indoc;
3090    use language::{Buffer, LanguageRegistry};
3091    use pretty_assertions::assert_eq;
3092    use prompt_store::PromptBuilder;
3093    use text::OffsetRangeExt;
3094    use unindent::Unindent;
3095    use util::path;
3096
3097    #[gpui::test]
3098    async fn test_copy_paste_whole_message(cx: &mut TestAppContext) {
3099        let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(vec![
3100            (Role::User, "What is the Zed editor?"),
3101            (
3102                Role::Assistant,
3103                "Zed is a modern, high-performance code editor designed from the ground up for speed and collaboration.",
3104            ),
3105            (Role::User, ""),
3106        ],cx).await;
3107
3108        // Select & Copy whole user message
3109        assert_copy_paste_text_thread_editor(
3110            &text_thread_editor,
3111            message_range(&context, 0, &mut cx),
3112            indoc! {"
3113                What is the Zed editor?
3114                Zed is a modern, high-performance code editor designed from the ground up for speed and collaboration.
3115                What is the Zed editor?
3116            "},
3117            &mut cx,
3118        );
3119
3120        // Select & Copy whole assistant message
3121        assert_copy_paste_text_thread_editor(
3122            &text_thread_editor,
3123            message_range(&context, 1, &mut cx),
3124            indoc! {"
3125                What is the Zed editor?
3126                Zed is a modern, high-performance code editor designed from the ground up for speed and collaboration.
3127                What is the Zed editor?
3128                Zed is a modern, high-performance code editor designed from the ground up for speed and collaboration.
3129            "},
3130            &mut cx,
3131        );
3132    }
3133
3134    #[gpui::test]
3135    async fn test_copy_paste_no_selection(cx: &mut TestAppContext) {
3136        let (context, text_thread_editor, mut cx) = setup_text_thread_editor_text(
3137            vec![
3138                (Role::User, "user1"),
3139                (Role::Assistant, "assistant1"),
3140                (Role::Assistant, "assistant2"),
3141                (Role::User, ""),
3142            ],
3143            cx,
3144        )
3145        .await;
3146
3147        // Copy and paste first assistant message
3148        let message_2_range = message_range(&context, 1, &mut cx);
3149        assert_copy_paste_text_thread_editor(
3150            &text_thread_editor,
3151            message_2_range.start..message_2_range.start,
3152            indoc! {"
3153                user1
3154                assistant1
3155                assistant2
3156                assistant1
3157            "},
3158            &mut cx,
3159        );
3160
3161        // Copy and cut second assistant message
3162        let message_3_range = message_range(&context, 2, &mut cx);
3163        assert_copy_paste_text_thread_editor(
3164            &text_thread_editor,
3165            message_3_range.start..message_3_range.start,
3166            indoc! {"
3167                user1
3168                assistant1
3169                assistant2
3170                assistant1
3171                assistant2
3172            "},
3173            &mut cx,
3174        );
3175    }
3176
3177    #[gpui::test]
3178    fn test_find_code_blocks(cx: &mut App) {
3179        let markdown = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
3180
3181        let buffer = cx.new(|cx| {
3182            let text = r#"
3183                line 0
3184                line 1
3185                ```rust
3186                fn main() {}
3187                ```
3188                line 5
3189                line 6
3190                line 7
3191                ```go
3192                func main() {}
3193                ```
3194                line 11
3195                ```
3196                this is plain text code block
3197                ```
3198
3199                ```go
3200                func another() {}
3201                ```
3202                line 19
3203            "#
3204            .unindent();
3205            let mut buffer = Buffer::local(text, cx);
3206            buffer.set_language(Some(markdown.clone()), cx);
3207            buffer
3208        });
3209        let snapshot = buffer.read(cx).snapshot();
3210
3211        let code_blocks = vec![
3212            Point::new(3, 0)..Point::new(4, 0),
3213            Point::new(9, 0)..Point::new(10, 0),
3214            Point::new(13, 0)..Point::new(14, 0),
3215            Point::new(17, 0)..Point::new(18, 0),
3216        ]
3217        .into_iter()
3218        .map(|range| snapshot.point_to_offset(range.start)..snapshot.point_to_offset(range.end))
3219        .collect::<Vec<_>>();
3220
3221        let expected_results = vec![
3222            (0, None),
3223            (1, None),
3224            (2, Some(code_blocks[0].clone())),
3225            (3, Some(code_blocks[0].clone())),
3226            (4, Some(code_blocks[0].clone())),
3227            (5, None),
3228            (6, None),
3229            (7, None),
3230            (8, Some(code_blocks[1].clone())),
3231            (9, Some(code_blocks[1].clone())),
3232            (10, Some(code_blocks[1].clone())),
3233            (11, None),
3234            (12, Some(code_blocks[2].clone())),
3235            (13, Some(code_blocks[2].clone())),
3236            (14, Some(code_blocks[2].clone())),
3237            (15, None),
3238            (16, Some(code_blocks[3].clone())),
3239            (17, Some(code_blocks[3].clone())),
3240            (18, Some(code_blocks[3].clone())),
3241            (19, None),
3242        ];
3243
3244        for (row, expected) in expected_results {
3245            let offset = snapshot.point_to_offset(Point::new(row, 0));
3246            let range = find_surrounding_code_block(&snapshot, offset);
3247            assert_eq!(range, expected, "unexpected result on row {:?}", row);
3248        }
3249    }
3250
3251    async fn setup_text_thread_editor_text(
3252        messages: Vec<(Role, &str)>,
3253        cx: &mut TestAppContext,
3254    ) -> (
3255        Entity<TextThread>,
3256        Entity<TextThreadEditor>,
3257        VisualTestContext,
3258    ) {
3259        cx.update(init_test);
3260
3261        let fs = FakeFs::new(cx.executor());
3262        let text_thread = create_text_thread_with_messages(messages, cx);
3263
3264        let project = Project::test(fs.clone(), [path!("/test").as_ref()], cx).await;
3265        let window = cx.add_window(|window, cx| Workspace::test_new(project.clone(), window, cx));
3266        let workspace = window.root(cx).unwrap();
3267        let mut cx = VisualTestContext::from_window(*window, cx);
3268
3269        let text_thread_editor = window
3270            .update(&mut cx, |_, window, cx| {
3271                cx.new(|cx| {
3272                    TextThreadEditor::for_text_thread(
3273                        text_thread.clone(),
3274                        fs,
3275                        workspace.downgrade(),
3276                        project,
3277                        None,
3278                        window,
3279                        cx,
3280                    )
3281                })
3282            })
3283            .unwrap();
3284
3285        (text_thread, text_thread_editor, cx)
3286    }
3287
3288    fn message_range(
3289        text_thread: &Entity<TextThread>,
3290        message_ix: usize,
3291        cx: &mut TestAppContext,
3292    ) -> Range<MultiBufferOffset> {
3293        let range = text_thread.update(cx, |text_thread, cx| {
3294            text_thread
3295                .messages(cx)
3296                .nth(message_ix)
3297                .unwrap()
3298                .anchor_range
3299                .to_offset(&text_thread.buffer().read(cx).snapshot())
3300        });
3301        MultiBufferOffset(range.start)..MultiBufferOffset(range.end)
3302    }
3303
3304    fn assert_copy_paste_text_thread_editor<T: editor::ToOffset>(
3305        text_thread_editor: &Entity<TextThreadEditor>,
3306        range: Range<T>,
3307        expected_text: &str,
3308        cx: &mut VisualTestContext,
3309    ) {
3310        text_thread_editor.update_in(cx, |text_thread_editor, window, cx| {
3311            text_thread_editor.editor.update(cx, |editor, cx| {
3312                editor.change_selections(SelectionEffects::no_scroll(), window, cx, |s| {
3313                    s.select_ranges([range])
3314                });
3315            });
3316
3317            text_thread_editor.copy(&Default::default(), window, cx);
3318
3319            text_thread_editor.editor.update(cx, |editor, cx| {
3320                editor.move_to_end(&Default::default(), window, cx);
3321            });
3322
3323            text_thread_editor.paste(&Default::default(), window, cx);
3324
3325            text_thread_editor.editor.update(cx, |editor, cx| {
3326                assert_eq!(editor.text(cx), expected_text);
3327            });
3328        });
3329    }
3330
3331    fn create_text_thread_with_messages(
3332        mut messages: Vec<(Role, &str)>,
3333        cx: &mut TestAppContext,
3334    ) -> Entity<TextThread> {
3335        let registry = Arc::new(LanguageRegistry::test(cx.executor()));
3336        let prompt_builder = Arc::new(PromptBuilder::new(None).unwrap());
3337        cx.new(|cx| {
3338            let mut text_thread = TextThread::local(
3339                registry,
3340                None,
3341                prompt_builder.clone(),
3342                Arc::new(SlashCommandWorkingSet::default()),
3343                cx,
3344            );
3345            let mut message_1 = text_thread.messages(cx).next().unwrap();
3346            let (role, text) = messages.remove(0);
3347
3348            loop {
3349                if role == message_1.role {
3350                    text_thread.buffer().update(cx, |buffer, cx| {
3351                        buffer.edit([(message_1.offset_range, text)], None, cx);
3352                    });
3353                    break;
3354                }
3355                let mut ids = HashSet::default();
3356                ids.insert(message_1.id);
3357                text_thread.cycle_message_roles(ids, cx);
3358                message_1 = text_thread.messages(cx).next().unwrap();
3359            }
3360
3361            let mut last_message_id = message_1.id;
3362            for (role, text) in messages {
3363                text_thread.insert_message_after(last_message_id, role, MessageStatus::Done, cx);
3364                let message = text_thread.messages(cx).last().unwrap();
3365                last_message_id = message.id;
3366                text_thread.buffer().update(cx, |buffer, cx| {
3367                    buffer.edit([(message.offset_range, text)], None, cx);
3368                })
3369            }
3370
3371            text_thread
3372        })
3373    }
3374
3375    fn init_test(cx: &mut App) {
3376        let settings_store = SettingsStore::test(cx);
3377        prompt_store::init(cx);
3378        LanguageModelRegistry::test(cx);
3379        cx.set_global(settings_store);
3380
3381        theme::init(theme::LoadThemes::JustBase, cx);
3382    }
3383}