context_editor.rs

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