context_editor.rs

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