context_editor.rs

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