context_editor.rs

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