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