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