text_thread_editor.rs

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