context_editor.rs

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