text_thread_editor.rs

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