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