assistant_panel.rs

   1use crate::{
   2    assistant_settings::{AssistantDockPosition, AssistantSettings},
   3    humanize_token_count,
   4    prompt_library::open_prompt_library,
   5    prompts::PromptBuilder,
   6    slash_command::{
   7        default_command::DefaultSlashCommand,
   8        docs_command::{DocsSlashCommand, DocsSlashCommandArgs},
   9        file_command::{self, codeblock_fence_for_path},
  10        SlashCommandCompletionProvider, SlashCommandRegistry,
  11    },
  12    slash_command_picker,
  13    terminal_inline_assistant::TerminalInlineAssistant,
  14    Assist, AssistantPatch, AssistantPatchStatus, CacheStatus, ConfirmCommand, Content, Context,
  15    ContextEvent, ContextId, ContextStore, ContextStoreEvent, CopyCode, CycleMessageRole,
  16    DeployHistory, DeployPromptLibrary, Edit, InlineAssistant, InsertDraggedFiles,
  17    InsertIntoEditor, Message, MessageId, MessageMetadata, MessageStatus, ModelPickerDelegate,
  18    ModelSelector, NewContext, PendingSlashCommand, PendingSlashCommandStatus, QuoteSelection,
  19    RemoteContextMetadata, RequestType, SavedContextMetadata, Split, ToggleFocus,
  20    ToggleModelSelector,
  21};
  22use anyhow::Result;
  23use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
  24use assistant_tool::ToolRegistry;
  25use client::{proto, zed_urls, Client, Status};
  26use collections::{BTreeSet, HashMap, HashSet};
  27use editor::{
  28    actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
  29    display_map::{
  30        BlockContext, BlockId, BlockPlacement, BlockProperties, BlockStyle, Crease, CreaseMetadata,
  31        CustomBlockId, FoldId, RenderBlock, ToDisplayPoint,
  32    },
  33    scroll::{Autoscroll, AutoscrollStrategy},
  34    Anchor, Editor, EditorEvent, ProposedChangeLocation, ProposedChangesEditor, RowExt,
  35    ToOffset as _, ToPoint,
  36};
  37use editor::{display_map::CreaseId, FoldPlaceholder};
  38use fs::Fs;
  39use futures::FutureExt;
  40use gpui::{
  41    canvas, div, img, percentage, point, pulsating_between, size, Action, Animation, AnimationExt,
  42    AnyElement, AnyView, AppContext, AsyncWindowContext, ClipboardEntry, ClipboardItem,
  43    CursorStyle, Empty, Entity, EventEmitter, ExternalPaths, FocusHandle, FocusableView,
  44    FontWeight, InteractiveElement, IntoElement, Model, ParentElement, Pixels, Render, RenderImage,
  45    SharedString, Size, StatefulInteractiveElement, Styled, Subscription, Task, Transformation,
  46    UpdateGlobal, View, VisualContext, WeakView, WindowContext,
  47};
  48use indexed_docs::IndexedDocsStore;
  49use language::{
  50    language_settings::SoftWrap, BufferSnapshot, LanguageRegistry, LspAdapterDelegate, ToOffset,
  51};
  52use language_model::{
  53    provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
  54    LanguageModelRegistry, Role,
  55};
  56use language_model::{LanguageModelImage, LanguageModelToolUse};
  57use multi_buffer::MultiBufferRow;
  58use picker::{Picker, PickerDelegate};
  59use project::lsp_store::LocalLspAdapterDelegate;
  60use project::{Project, Worktree};
  61use rope::Point;
  62use search::{buffer_search::DivRegistrar, BufferSearchBar};
  63use serde::{Deserialize, Serialize};
  64use settings::{update_settings_file, Settings};
  65use smol::stream::StreamExt;
  66use std::{
  67    any::TypeId,
  68    borrow::Cow,
  69    cmp,
  70    ops::{ControlFlow, Range},
  71    path::PathBuf,
  72    sync::Arc,
  73    time::Duration,
  74};
  75use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
  76use text::SelectionGoal;
  77use ui::{
  78    prelude::*,
  79    utils::{format_distance_from_now, DateTimeType},
  80    Avatar, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
  81    ListItemSpacing, PopoverMenu, PopoverMenuHandle, TintColor, Tooltip,
  82};
  83use util::{maybe, ResultExt};
  84use workspace::{
  85    dock::{DockPosition, Panel, PanelEvent},
  86    item::{self, FollowableItem, Item, ItemHandle},
  87    notifications::NotificationId,
  88    pane::{self, SaveIntent},
  89    searchable::{SearchEvent, SearchableItem},
  90    DraggedSelection, Pane, Save, ShowConfiguration, Toast, ToggleZoom, ToolbarItemEvent,
  91    ToolbarItemLocation, ToolbarItemView, Workspace,
  92};
  93use workspace::{searchable::SearchableItemHandle, DraggedTab};
  94use zed_actions::InlineAssist;
  95
  96pub fn init(cx: &mut AppContext) {
  97    workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
  98    cx.observe_new_views(
  99        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
 100            workspace
 101                .register_action(|workspace, _: &ToggleFocus, cx| {
 102                    let settings = AssistantSettings::get_global(cx);
 103                    if !settings.enabled {
 104                        return;
 105                    }
 106
 107                    workspace.toggle_panel_focus::<AssistantPanel>(cx);
 108                })
 109                .register_action(AssistantPanel::inline_assist)
 110                .register_action(ContextEditor::quote_selection)
 111                .register_action(ContextEditor::insert_selection)
 112                .register_action(ContextEditor::copy_code)
 113                .register_action(ContextEditor::insert_dragged_files)
 114                .register_action(AssistantPanel::show_configuration)
 115                .register_action(AssistantPanel::create_new_context);
 116        },
 117    )
 118    .detach();
 119
 120    cx.observe_new_views(
 121        |terminal_panel: &mut TerminalPanel, cx: &mut ViewContext<TerminalPanel>| {
 122            let settings = AssistantSettings::get_global(cx);
 123            terminal_panel.asssistant_enabled(settings.enabled, cx);
 124        },
 125    )
 126    .detach();
 127}
 128
 129pub enum AssistantPanelEvent {
 130    ContextEdited,
 131}
 132
 133pub struct AssistantPanel {
 134    pane: View<Pane>,
 135    workspace: WeakView<Workspace>,
 136    width: Option<Pixels>,
 137    height: Option<Pixels>,
 138    project: Model<Project>,
 139    context_store: Model<ContextStore>,
 140    languages: Arc<LanguageRegistry>,
 141    fs: Arc<dyn Fs>,
 142    subscriptions: Vec<Subscription>,
 143    model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
 144    model_summary_editor: View<Editor>,
 145    authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
 146    configuration_subscription: Option<Subscription>,
 147    client_status: Option<client::Status>,
 148    watch_client_status: Option<Task<()>>,
 149    show_zed_ai_notice: bool,
 150}
 151
 152#[derive(Clone)]
 153enum ContextMetadata {
 154    Remote(RemoteContextMetadata),
 155    Saved(SavedContextMetadata),
 156}
 157
 158struct SavedContextPickerDelegate {
 159    store: Model<ContextStore>,
 160    project: Model<Project>,
 161    matches: Vec<ContextMetadata>,
 162    selected_index: usize,
 163}
 164
 165enum SavedContextPickerEvent {
 166    Confirmed(ContextMetadata),
 167}
 168
 169enum InlineAssistTarget {
 170    Editor(View<Editor>, bool),
 171    Terminal(View<TerminalView>),
 172}
 173
 174impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
 175
 176impl SavedContextPickerDelegate {
 177    fn new(project: Model<Project>, store: Model<ContextStore>) -> Self {
 178        Self {
 179            project,
 180            store,
 181            matches: Vec::new(),
 182            selected_index: 0,
 183        }
 184    }
 185}
 186
 187impl PickerDelegate for SavedContextPickerDelegate {
 188    type ListItem = ListItem;
 189
 190    fn match_count(&self) -> usize {
 191        self.matches.len()
 192    }
 193
 194    fn selected_index(&self) -> usize {
 195        self.selected_index
 196    }
 197
 198    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
 199        self.selected_index = ix;
 200    }
 201
 202    fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
 203        "Search...".into()
 204    }
 205
 206    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
 207        let search = self.store.read(cx).search(query, cx);
 208        cx.spawn(|this, mut cx| async move {
 209            let matches = search.await;
 210            this.update(&mut cx, |this, cx| {
 211                let host_contexts = this.delegate.store.read(cx).host_contexts();
 212                this.delegate.matches = host_contexts
 213                    .iter()
 214                    .cloned()
 215                    .map(ContextMetadata::Remote)
 216                    .chain(matches.into_iter().map(ContextMetadata::Saved))
 217                    .collect();
 218                this.delegate.selected_index = 0;
 219                cx.notify();
 220            })
 221            .ok();
 222        })
 223    }
 224
 225    fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
 226        if let Some(metadata) = self.matches.get(self.selected_index) {
 227            cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone()));
 228        }
 229    }
 230
 231    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
 232
 233    fn render_match(
 234        &self,
 235        ix: usize,
 236        selected: bool,
 237        cx: &mut ViewContext<Picker<Self>>,
 238    ) -> Option<Self::ListItem> {
 239        let context = self.matches.get(ix)?;
 240        let item = match context {
 241            ContextMetadata::Remote(context) => {
 242                let host_user = self.project.read(cx).host().and_then(|collaborator| {
 243                    self.project
 244                        .read(cx)
 245                        .user_store()
 246                        .read(cx)
 247                        .get_cached_user(collaborator.user_id)
 248                });
 249                div()
 250                    .flex()
 251                    .w_full()
 252                    .justify_between()
 253                    .gap_2()
 254                    .child(
 255                        h_flex().flex_1().overflow_x_hidden().child(
 256                            Label::new(context.summary.clone().unwrap_or(DEFAULT_TAB_TITLE.into()))
 257                                .size(LabelSize::Small),
 258                        ),
 259                    )
 260                    .child(
 261                        h_flex()
 262                            .gap_2()
 263                            .children(if let Some(host_user) = host_user {
 264                                vec![
 265                                    Avatar::new(host_user.avatar_uri.clone()).into_any_element(),
 266                                    Label::new(format!("Shared by @{}", host_user.github_login))
 267                                        .color(Color::Muted)
 268                                        .size(LabelSize::Small)
 269                                        .into_any_element(),
 270                                ]
 271                            } else {
 272                                vec![Label::new("Shared by host")
 273                                    .color(Color::Muted)
 274                                    .size(LabelSize::Small)
 275                                    .into_any_element()]
 276                            }),
 277                    )
 278            }
 279            ContextMetadata::Saved(context) => div()
 280                .flex()
 281                .w_full()
 282                .justify_between()
 283                .gap_2()
 284                .child(
 285                    h_flex()
 286                        .flex_1()
 287                        .child(Label::new(context.title.clone()).size(LabelSize::Small))
 288                        .overflow_x_hidden(),
 289                )
 290                .child(
 291                    Label::new(format_distance_from_now(
 292                        DateTimeType::Local(context.mtime),
 293                        false,
 294                        true,
 295                        true,
 296                    ))
 297                    .color(Color::Muted)
 298                    .size(LabelSize::Small),
 299                ),
 300        };
 301        Some(
 302            ListItem::new(ix)
 303                .inset(true)
 304                .spacing(ListItemSpacing::Sparse)
 305                .selected(selected)
 306                .child(item),
 307        )
 308    }
 309}
 310
 311impl AssistantPanel {
 312    pub fn load(
 313        workspace: WeakView<Workspace>,
 314        prompt_builder: Arc<PromptBuilder>,
 315        cx: AsyncWindowContext,
 316    ) -> Task<Result<View<Self>>> {
 317        cx.spawn(|mut cx| async move {
 318            let context_store = workspace
 319                .update(&mut cx, |workspace, cx| {
 320                    let project = workspace.project().clone();
 321                    ContextStore::new(project, prompt_builder.clone(), cx)
 322                })?
 323                .await?;
 324
 325            workspace.update(&mut cx, |workspace, cx| {
 326                // TODO: deserialize state.
 327                cx.new_view(|cx| Self::new(workspace, context_store, cx))
 328            })
 329        })
 330    }
 331
 332    fn new(
 333        workspace: &Workspace,
 334        context_store: Model<ContextStore>,
 335        cx: &mut ViewContext<Self>,
 336    ) -> Self {
 337        let model_selector_menu_handle = PopoverMenuHandle::default();
 338        let model_summary_editor = cx.new_view(Editor::single_line);
 339        let context_editor_toolbar = cx.new_view(|_| {
 340            ContextEditorToolbarItem::new(
 341                workspace,
 342                model_selector_menu_handle.clone(),
 343                model_summary_editor.clone(),
 344            )
 345        });
 346
 347        let pane = cx.new_view(|cx| {
 348            let mut pane = Pane::new(
 349                workspace.weak_handle(),
 350                workspace.project().clone(),
 351                Default::default(),
 352                None,
 353                NewContext.boxed_clone(),
 354                cx,
 355            );
 356
 357            let project = workspace.project().clone();
 358            pane.set_custom_drop_handle(cx, move |_, dropped_item, cx| {
 359                let action = maybe!({
 360                    if project.read(cx).is_local() {
 361                        if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {
 362                            return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec()));
 363                        }
 364                    }
 365
 366                    let project_paths = if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>()
 367                    {
 368                        if &tab.pane == cx.view() {
 369                            return None;
 370                        }
 371                        let item = tab.pane.read(cx).item_for_index(tab.ix);
 372                        Some(
 373                            item.and_then(|item| item.project_path(cx))
 374                                .into_iter()
 375                                .collect::<Vec<_>>(),
 376                        )
 377                    } else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>()
 378                    {
 379                        Some(
 380                            selection
 381                                .items()
 382                                .filter_map(|item| {
 383                                    project.read(cx).path_for_entry(item.entry_id, cx)
 384                                })
 385                                .collect::<Vec<_>>(),
 386                        )
 387                    } else {
 388                        None
 389                    }?;
 390
 391                    let paths = project_paths
 392                        .into_iter()
 393                        .filter_map(|project_path| {
 394                            let worktree = project
 395                                .read(cx)
 396                                .worktree_for_id(project_path.worktree_id, cx)?;
 397
 398                            let mut full_path = PathBuf::from(worktree.read(cx).root_name());
 399                            full_path.push(&project_path.path);
 400                            Some(full_path)
 401                        })
 402                        .collect::<Vec<_>>();
 403
 404                    Some(InsertDraggedFiles::ProjectPaths(paths))
 405                });
 406
 407                if let Some(action) = action {
 408                    cx.dispatch_action(action.boxed_clone());
 409                }
 410
 411                ControlFlow::Break(())
 412            });
 413
 414            pane.set_can_split(false, cx);
 415            pane.set_can_navigate(true, cx);
 416            pane.display_nav_history_buttons(None);
 417            pane.set_should_display_tab_bar(|_| true);
 418            pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
 419                let focus_handle = pane.focus_handle(cx);
 420                let left_children = IconButton::new("history", IconName::HistoryRerun)
 421                    .icon_size(IconSize::Small)
 422                    .on_click(cx.listener({
 423                        let focus_handle = focus_handle.clone();
 424                        move |_, _, cx| {
 425                            focus_handle.focus(cx);
 426                            cx.dispatch_action(DeployHistory.boxed_clone())
 427                        }
 428                    }))
 429                    .tooltip({
 430                        let focus_handle = focus_handle.clone();
 431                        move |cx| {
 432                            Tooltip::for_action_in(
 433                                "Open History",
 434                                &DeployHistory,
 435                                &focus_handle,
 436                                cx,
 437                            )
 438                        }
 439                    })
 440                    .selected(
 441                        pane.active_item()
 442                            .map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
 443                    );
 444                let _pane = cx.view().clone();
 445                let right_children = h_flex()
 446                    .gap(Spacing::XSmall.rems(cx))
 447                    .child(
 448                        IconButton::new("new-chat", IconName::Plus)
 449                            .on_click(
 450                                cx.listener(|_, _, cx| {
 451                                    cx.dispatch_action(NewContext.boxed_clone())
 452                                }),
 453                            )
 454                            .tooltip(move |cx| {
 455                                Tooltip::for_action_in("New Chat", &NewContext, &focus_handle, cx)
 456                            }),
 457                    )
 458                    .child(
 459                        PopoverMenu::new("assistant-panel-popover-menu")
 460                            .trigger(
 461                                IconButton::new("menu", IconName::EllipsisVertical)
 462                                    .icon_size(IconSize::Small)
 463                                    .tooltip(|cx| Tooltip::text("Toggle Assistant Menu", cx)),
 464                            )
 465                            .menu(move |cx| {
 466                                let zoom_label = if _pane.read(cx).is_zoomed() {
 467                                    "Zoom Out"
 468                                } else {
 469                                    "Zoom In"
 470                                };
 471                                let focus_handle = _pane.focus_handle(cx);
 472                                Some(ContextMenu::build(cx, move |menu, _| {
 473                                    menu.context(focus_handle.clone())
 474                                        .action("New Chat", Box::new(NewContext))
 475                                        .action("History", Box::new(DeployHistory))
 476                                        .action("Prompt Library", Box::new(DeployPromptLibrary))
 477                                        .action("Configure", Box::new(ShowConfiguration))
 478                                        .action(zoom_label, Box::new(ToggleZoom))
 479                                }))
 480                            }),
 481                    )
 482                    .into_any_element()
 483                    .into();
 484
 485                (Some(left_children.into_any_element()), right_children)
 486            });
 487            pane.toolbar().update(cx, |toolbar, cx| {
 488                toolbar.add_item(context_editor_toolbar.clone(), cx);
 489                toolbar.add_item(cx.new_view(BufferSearchBar::new), cx)
 490            });
 491            pane
 492        });
 493
 494        let subscriptions = vec![
 495            cx.observe(&pane, |_, _, cx| cx.notify()),
 496            cx.subscribe(&pane, Self::handle_pane_event),
 497            cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),
 498            cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),
 499            cx.subscribe(&context_store, Self::handle_context_store_event),
 500            cx.subscribe(
 501                &LanguageModelRegistry::global(cx),
 502                |this, _, event: &language_model::Event, cx| match event {
 503                    language_model::Event::ActiveModelChanged => {
 504                        this.completion_provider_changed(cx);
 505                    }
 506                    language_model::Event::ProviderStateChanged => {
 507                        this.ensure_authenticated(cx);
 508                        cx.notify()
 509                    }
 510                    language_model::Event::AddedProvider(_)
 511                    | language_model::Event::RemovedProvider(_) => {
 512                        this.ensure_authenticated(cx);
 513                    }
 514                },
 515            ),
 516        ];
 517
 518        let watch_client_status = Self::watch_client_status(workspace.client().clone(), cx);
 519
 520        let mut this = Self {
 521            pane,
 522            workspace: workspace.weak_handle(),
 523            width: None,
 524            height: None,
 525            project: workspace.project().clone(),
 526            context_store,
 527            languages: workspace.app_state().languages.clone(),
 528            fs: workspace.app_state().fs.clone(),
 529            subscriptions,
 530            model_selector_menu_handle,
 531            model_summary_editor,
 532            authenticate_provider_task: None,
 533            configuration_subscription: None,
 534            client_status: None,
 535            watch_client_status: Some(watch_client_status),
 536            show_zed_ai_notice: false,
 537        };
 538        this.new_context(cx);
 539        this
 540    }
 541
 542    fn watch_client_status(client: Arc<Client>, cx: &mut ViewContext<Self>) -> Task<()> {
 543        let mut status_rx = client.status();
 544
 545        cx.spawn(|this, mut cx| async move {
 546            while let Some(status) = status_rx.next().await {
 547                this.update(&mut cx, |this, cx| {
 548                    if this.client_status.is_none()
 549                        || this
 550                            .client_status
 551                            .map_or(false, |old_status| old_status != status)
 552                    {
 553                        this.update_zed_ai_notice_visibility(status, cx);
 554                    }
 555                    this.client_status = Some(status);
 556                })
 557                .log_err();
 558            }
 559            this.update(&mut cx, |this, _cx| this.watch_client_status = None)
 560                .log_err();
 561        })
 562    }
 563
 564    fn handle_pane_event(
 565        &mut self,
 566        pane: View<Pane>,
 567        event: &pane::Event,
 568        cx: &mut ViewContext<Self>,
 569    ) {
 570        let update_model_summary = match event {
 571            pane::Event::Remove { .. } => {
 572                cx.emit(PanelEvent::Close);
 573                false
 574            }
 575            pane::Event::ZoomIn => {
 576                cx.emit(PanelEvent::ZoomIn);
 577                false
 578            }
 579            pane::Event::ZoomOut => {
 580                cx.emit(PanelEvent::ZoomOut);
 581                false
 582            }
 583
 584            pane::Event::AddItem { item } => {
 585                self.workspace
 586                    .update(cx, |workspace, cx| {
 587                        item.added_to_pane(workspace, self.pane.clone(), cx)
 588                    })
 589                    .ok();
 590                true
 591            }
 592
 593            pane::Event::ActivateItem { local } => {
 594                if *local {
 595                    self.workspace
 596                        .update(cx, |workspace, cx| {
 597                            workspace.unfollow_in_pane(&pane, cx);
 598                        })
 599                        .ok();
 600                }
 601                cx.emit(AssistantPanelEvent::ContextEdited);
 602                true
 603            }
 604            pane::Event::RemovedItem { .. } => {
 605                let has_configuration_view = self
 606                    .pane
 607                    .read(cx)
 608                    .items_of_type::<ConfigurationView>()
 609                    .next()
 610                    .is_some();
 611
 612                if !has_configuration_view {
 613                    self.configuration_subscription = None;
 614                }
 615
 616                cx.emit(AssistantPanelEvent::ContextEdited);
 617                true
 618            }
 619
 620            _ => false,
 621        };
 622
 623        if update_model_summary {
 624            if let Some(editor) = self.active_context_editor(cx) {
 625                self.show_updated_summary(&editor, cx)
 626            }
 627        }
 628    }
 629
 630    fn handle_summary_editor_event(
 631        &mut self,
 632        model_summary_editor: View<Editor>,
 633        event: &EditorEvent,
 634        cx: &mut ViewContext<Self>,
 635    ) {
 636        if matches!(event, EditorEvent::Edited { .. }) {
 637            if let Some(context_editor) = self.active_context_editor(cx) {
 638                let new_summary = model_summary_editor.read(cx).text(cx);
 639                context_editor.update(cx, |context_editor, cx| {
 640                    context_editor.context.update(cx, |context, cx| {
 641                        if context.summary().is_none()
 642                            && (new_summary == DEFAULT_TAB_TITLE || new_summary.trim().is_empty())
 643                        {
 644                            return;
 645                        }
 646                        context.custom_summary(new_summary, cx)
 647                    });
 648                });
 649            }
 650        }
 651    }
 652
 653    fn update_zed_ai_notice_visibility(
 654        &mut self,
 655        client_status: Status,
 656        cx: &mut ViewContext<Self>,
 657    ) {
 658        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
 659
 660        // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
 661        // the provider, we want to show a nudge to sign in.
 662        let show_zed_ai_notice = client_status.is_signed_out()
 663            && active_provider.map_or(true, |provider| provider.id().0 == PROVIDER_ID);
 664
 665        self.show_zed_ai_notice = show_zed_ai_notice;
 666        cx.notify();
 667    }
 668
 669    fn handle_toolbar_event(
 670        &mut self,
 671        _: View<ContextEditorToolbarItem>,
 672        _: &ContextEditorToolbarItemEvent,
 673        cx: &mut ViewContext<Self>,
 674    ) {
 675        if let Some(context_editor) = self.active_context_editor(cx) {
 676            context_editor.update(cx, |context_editor, cx| {
 677                context_editor.context.update(cx, |context, cx| {
 678                    context.summarize(true, cx);
 679                })
 680            })
 681        }
 682    }
 683
 684    fn handle_context_store_event(
 685        &mut self,
 686        _context_store: Model<ContextStore>,
 687        event: &ContextStoreEvent,
 688        cx: &mut ViewContext<Self>,
 689    ) {
 690        let ContextStoreEvent::ContextCreated(context_id) = event;
 691        let Some(context) = self
 692            .context_store
 693            .read(cx)
 694            .loaded_context_for_id(&context_id, cx)
 695        else {
 696            log::error!("no context found with ID: {}", context_id.to_proto());
 697            return;
 698        };
 699        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 700            .log_err()
 701            .flatten();
 702
 703        let assistant_panel = cx.view().downgrade();
 704        let editor = cx.new_view(|cx| {
 705            let mut editor = ContextEditor::for_context(
 706                context,
 707                self.fs.clone(),
 708                self.workspace.clone(),
 709                self.project.clone(),
 710                lsp_adapter_delegate,
 711                assistant_panel,
 712                cx,
 713            );
 714            editor.insert_default_prompt(cx);
 715            editor
 716        });
 717
 718        self.show_context(editor.clone(), cx);
 719    }
 720
 721    fn completion_provider_changed(&mut self, cx: &mut ViewContext<Self>) {
 722        if let Some(editor) = self.active_context_editor(cx) {
 723            editor.update(cx, |active_context, cx| {
 724                active_context
 725                    .context
 726                    .update(cx, |context, cx| context.completion_provider_changed(cx))
 727            })
 728        }
 729
 730        let Some(new_provider_id) = LanguageModelRegistry::read_global(cx)
 731            .active_provider()
 732            .map(|p| p.id())
 733        else {
 734            return;
 735        };
 736
 737        if self
 738            .authenticate_provider_task
 739            .as_ref()
 740            .map_or(true, |(old_provider_id, _)| {
 741                *old_provider_id != new_provider_id
 742            })
 743        {
 744            self.authenticate_provider_task = None;
 745            self.ensure_authenticated(cx);
 746        }
 747
 748        if let Some(status) = self.client_status {
 749            self.update_zed_ai_notice_visibility(status, cx);
 750        }
 751    }
 752
 753    fn ensure_authenticated(&mut self, cx: &mut ViewContext<Self>) {
 754        if self.is_authenticated(cx) {
 755            return;
 756        }
 757
 758        let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
 759            return;
 760        };
 761
 762        let load_credentials = self.authenticate(cx);
 763
 764        if self.authenticate_provider_task.is_none() {
 765            self.authenticate_provider_task = Some((
 766                provider.id(),
 767                cx.spawn(|this, mut cx| async move {
 768                    if let Some(future) = load_credentials {
 769                        let _ = future.await;
 770                    }
 771                    this.update(&mut cx, |this, _cx| {
 772                        this.authenticate_provider_task = None;
 773                    })
 774                    .log_err();
 775                }),
 776            ));
 777        }
 778    }
 779
 780    pub fn inline_assist(
 781        workspace: &mut Workspace,
 782        action: &InlineAssist,
 783        cx: &mut ViewContext<Workspace>,
 784    ) {
 785        let settings = AssistantSettings::get_global(cx);
 786        if !settings.enabled {
 787            return;
 788        }
 789
 790        let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
 791            return;
 792        };
 793
 794        let Some(inline_assist_target) =
 795            Self::resolve_inline_assist_target(workspace, &assistant_panel, cx)
 796        else {
 797            return;
 798        };
 799
 800        let initial_prompt = action.prompt.clone();
 801
 802        if assistant_panel.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
 803            match inline_assist_target {
 804                InlineAssistTarget::Editor(active_editor, include_context) => {
 805                    InlineAssistant::update_global(cx, |assistant, cx| {
 806                        assistant.assist(
 807                            &active_editor,
 808                            Some(cx.view().downgrade()),
 809                            include_context.then_some(&assistant_panel),
 810                            initial_prompt,
 811                            cx,
 812                        )
 813                    })
 814                }
 815                InlineAssistTarget::Terminal(active_terminal) => {
 816                    TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 817                        assistant.assist(
 818                            &active_terminal,
 819                            Some(cx.view().downgrade()),
 820                            Some(&assistant_panel),
 821                            initial_prompt,
 822                            cx,
 823                        )
 824                    })
 825                }
 826            }
 827        } else {
 828            let assistant_panel = assistant_panel.downgrade();
 829            cx.spawn(|workspace, mut cx| async move {
 830                let Some(task) =
 831                    assistant_panel.update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
 832                else {
 833                    let answer = cx
 834                        .prompt(
 835                            gpui::PromptLevel::Warning,
 836                            "No language model provider configured",
 837                            None,
 838                            &["Configure", "Cancel"],
 839                        )
 840                        .await
 841                        .ok();
 842                    if let Some(answer) = answer {
 843                        if answer == 0 {
 844                            cx.update(|cx| cx.dispatch_action(Box::new(ShowConfiguration)))
 845                                .ok();
 846                        }
 847                    }
 848                    return Ok(());
 849                };
 850                task.await?;
 851                if assistant_panel.update(&mut cx, |panel, cx| panel.is_authenticated(cx))? {
 852                    cx.update(|cx| match inline_assist_target {
 853                        InlineAssistTarget::Editor(active_editor, include_context) => {
 854                            let assistant_panel = if include_context {
 855                                assistant_panel.upgrade()
 856                            } else {
 857                                None
 858                            };
 859                            InlineAssistant::update_global(cx, |assistant, cx| {
 860                                assistant.assist(
 861                                    &active_editor,
 862                                    Some(workspace),
 863                                    assistant_panel.as_ref(),
 864                                    initial_prompt,
 865                                    cx,
 866                                )
 867                            })
 868                        }
 869                        InlineAssistTarget::Terminal(active_terminal) => {
 870                            TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 871                                assistant.assist(
 872                                    &active_terminal,
 873                                    Some(workspace),
 874                                    assistant_panel.upgrade().as_ref(),
 875                                    initial_prompt,
 876                                    cx,
 877                                )
 878                            })
 879                        }
 880                    })?
 881                } else {
 882                    workspace.update(&mut cx, |workspace, cx| {
 883                        workspace.focus_panel::<AssistantPanel>(cx)
 884                    })?;
 885                }
 886
 887                anyhow::Ok(())
 888            })
 889            .detach_and_log_err(cx)
 890        }
 891    }
 892
 893    fn resolve_inline_assist_target(
 894        workspace: &mut Workspace,
 895        assistant_panel: &View<AssistantPanel>,
 896        cx: &mut WindowContext,
 897    ) -> Option<InlineAssistTarget> {
 898        if let Some(terminal_panel) = workspace.panel::<TerminalPanel>(cx) {
 899            if terminal_panel
 900                .read(cx)
 901                .focus_handle(cx)
 902                .contains_focused(cx)
 903            {
 904                if let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| {
 905                    pane.read(cx)
 906                        .active_item()
 907                        .and_then(|t| t.downcast::<TerminalView>())
 908                }) {
 909                    return Some(InlineAssistTarget::Terminal(terminal_view));
 910                }
 911            }
 912        }
 913        let context_editor =
 914            assistant_panel
 915                .read(cx)
 916                .active_context_editor(cx)
 917                .and_then(|editor| {
 918                    let editor = &editor.read(cx).editor;
 919                    if editor.read(cx).is_focused(cx) {
 920                        Some(editor.clone())
 921                    } else {
 922                        None
 923                    }
 924                });
 925
 926        if let Some(context_editor) = context_editor {
 927            Some(InlineAssistTarget::Editor(context_editor, false))
 928        } else if let Some(workspace_editor) = workspace
 929            .active_item(cx)
 930            .and_then(|item| item.act_as::<Editor>(cx))
 931        {
 932            Some(InlineAssistTarget::Editor(workspace_editor, true))
 933        } else if let Some(terminal_view) = workspace
 934            .active_item(cx)
 935            .and_then(|item| item.act_as::<TerminalView>(cx))
 936        {
 937            Some(InlineAssistTarget::Terminal(terminal_view))
 938        } else {
 939            None
 940        }
 941    }
 942
 943    pub fn create_new_context(
 944        workspace: &mut Workspace,
 945        _: &NewContext,
 946        cx: &mut ViewContext<Workspace>,
 947    ) {
 948        if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 949            let did_create_context = panel
 950                .update(cx, |panel, cx| {
 951                    panel.new_context(cx)?;
 952
 953                    Some(())
 954                })
 955                .is_some();
 956            if did_create_context {
 957                ContextEditor::quote_selection(workspace, &Default::default(), cx);
 958            }
 959        }
 960    }
 961
 962    fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
 963        let project = self.project.read(cx);
 964        if project.is_via_collab() {
 965            let task = self
 966                .context_store
 967                .update(cx, |store, cx| store.create_remote_context(cx));
 968
 969            cx.spawn(|this, mut cx| async move {
 970                let context = task.await?;
 971
 972                this.update(&mut cx, |this, cx| {
 973                    let workspace = this.workspace.clone();
 974                    let project = this.project.clone();
 975                    let lsp_adapter_delegate =
 976                        make_lsp_adapter_delegate(&project, cx).log_err().flatten();
 977
 978                    let fs = this.fs.clone();
 979                    let project = this.project.clone();
 980                    let weak_assistant_panel = cx.view().downgrade();
 981
 982                    let editor = cx.new_view(|cx| {
 983                        ContextEditor::for_context(
 984                            context,
 985                            fs,
 986                            workspace,
 987                            project,
 988                            lsp_adapter_delegate,
 989                            weak_assistant_panel,
 990                            cx,
 991                        )
 992                    });
 993
 994                    this.show_context(editor, cx);
 995
 996                    anyhow::Ok(())
 997                })??;
 998
 999                anyhow::Ok(())
1000            })
1001            .detach_and_log_err(cx);
1002
1003            None
1004        } else {
1005            let context = self.context_store.update(cx, |store, cx| store.create(cx));
1006            let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
1007                .log_err()
1008                .flatten();
1009
1010            let assistant_panel = cx.view().downgrade();
1011            let editor = cx.new_view(|cx| {
1012                let mut editor = ContextEditor::for_context(
1013                    context,
1014                    self.fs.clone(),
1015                    self.workspace.clone(),
1016                    self.project.clone(),
1017                    lsp_adapter_delegate,
1018                    assistant_panel,
1019                    cx,
1020                );
1021                editor.insert_default_prompt(cx);
1022                editor
1023            });
1024
1025            self.show_context(editor.clone(), cx);
1026            let workspace = self.workspace.clone();
1027            cx.spawn(move |_, mut cx| async move {
1028                workspace
1029                    .update(&mut cx, |workspace, cx| {
1030                        workspace.focus_panel::<AssistantPanel>(cx);
1031                    })
1032                    .ok();
1033            })
1034            .detach();
1035            Some(editor)
1036        }
1037    }
1038
1039    fn show_context(&mut self, context_editor: View<ContextEditor>, cx: &mut ViewContext<Self>) {
1040        let focus = self.focus_handle(cx).contains_focused(cx);
1041        let prev_len = self.pane.read(cx).items_len();
1042        self.pane.update(cx, |pane, cx| {
1043            pane.add_item(Box::new(context_editor.clone()), focus, focus, None, cx)
1044        });
1045
1046        if prev_len != self.pane.read(cx).items_len() {
1047            self.subscriptions
1048                .push(cx.subscribe(&context_editor, Self::handle_context_editor_event));
1049        }
1050
1051        self.show_updated_summary(&context_editor, cx);
1052
1053        cx.emit(AssistantPanelEvent::ContextEdited);
1054        cx.notify();
1055    }
1056
1057    fn show_updated_summary(
1058        &self,
1059        context_editor: &View<ContextEditor>,
1060        cx: &mut ViewContext<Self>,
1061    ) {
1062        context_editor.update(cx, |context_editor, cx| {
1063            let new_summary = context_editor.title(cx).to_string();
1064            self.model_summary_editor.update(cx, |summary_editor, cx| {
1065                if summary_editor.text(cx) != new_summary {
1066                    summary_editor.set_text(new_summary, cx);
1067                }
1068            });
1069        });
1070    }
1071
1072    fn handle_context_editor_event(
1073        &mut self,
1074        context_editor: View<ContextEditor>,
1075        event: &EditorEvent,
1076        cx: &mut ViewContext<Self>,
1077    ) {
1078        match event {
1079            EditorEvent::TitleChanged => {
1080                self.show_updated_summary(&context_editor, cx);
1081                cx.notify()
1082            }
1083            EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
1084            _ => {}
1085        }
1086    }
1087
1088    fn show_configuration(
1089        workspace: &mut Workspace,
1090        _: &ShowConfiguration,
1091        cx: &mut ViewContext<Workspace>,
1092    ) {
1093        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
1094            return;
1095        };
1096
1097        if !panel.focus_handle(cx).contains_focused(cx) {
1098            workspace.toggle_panel_focus::<AssistantPanel>(cx);
1099        }
1100
1101        panel.update(cx, |this, cx| {
1102            this.show_configuration_tab(cx);
1103        })
1104    }
1105
1106    fn show_configuration_tab(&mut self, cx: &mut ViewContext<Self>) {
1107        let configuration_item_ix = self
1108            .pane
1109            .read(cx)
1110            .items()
1111            .position(|item| item.downcast::<ConfigurationView>().is_some());
1112
1113        if let Some(configuration_item_ix) = configuration_item_ix {
1114            self.pane.update(cx, |pane, cx| {
1115                pane.activate_item(configuration_item_ix, true, true, cx);
1116            });
1117        } else {
1118            let configuration = cx.new_view(ConfigurationView::new);
1119            self.configuration_subscription = Some(cx.subscribe(
1120                &configuration,
1121                |this, _, event: &ConfigurationViewEvent, cx| match event {
1122                    ConfigurationViewEvent::NewProviderContextEditor(provider) => {
1123                        if LanguageModelRegistry::read_global(cx)
1124                            .active_provider()
1125                            .map_or(true, |p| p.id() != provider.id())
1126                        {
1127                            if let Some(model) = provider.provided_models(cx).first().cloned() {
1128                                update_settings_file::<AssistantSettings>(
1129                                    this.fs.clone(),
1130                                    cx,
1131                                    move |settings, _| settings.set_model(model),
1132                                );
1133                            }
1134                        }
1135
1136                        this.new_context(cx);
1137                    }
1138                },
1139            ));
1140            self.pane.update(cx, |pane, cx| {
1141                pane.add_item(Box::new(configuration), true, true, None, cx);
1142            });
1143        }
1144    }
1145
1146    fn deploy_history(&mut self, _: &DeployHistory, cx: &mut ViewContext<Self>) {
1147        let history_item_ix = self
1148            .pane
1149            .read(cx)
1150            .items()
1151            .position(|item| item.downcast::<ContextHistory>().is_some());
1152
1153        if let Some(history_item_ix) = history_item_ix {
1154            self.pane.update(cx, |pane, cx| {
1155                pane.activate_item(history_item_ix, true, true, cx);
1156            });
1157        } else {
1158            let assistant_panel = cx.view().downgrade();
1159            let history = cx.new_view(|cx| {
1160                ContextHistory::new(
1161                    self.project.clone(),
1162                    self.context_store.clone(),
1163                    assistant_panel,
1164                    cx,
1165                )
1166            });
1167            self.pane.update(cx, |pane, cx| {
1168                pane.add_item(Box::new(history), true, true, None, cx);
1169            });
1170        }
1171    }
1172
1173    fn deploy_prompt_library(&mut self, _: &DeployPromptLibrary, cx: &mut ViewContext<Self>) {
1174        open_prompt_library(self.languages.clone(), cx).detach_and_log_err(cx);
1175    }
1176
1177    fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
1178        self.model_selector_menu_handle.toggle(cx);
1179    }
1180
1181    fn active_context_editor(&self, cx: &AppContext) -> Option<View<ContextEditor>> {
1182        self.pane
1183            .read(cx)
1184            .active_item()?
1185            .downcast::<ContextEditor>()
1186    }
1187
1188    pub fn active_context(&self, cx: &AppContext) -> Option<Model<Context>> {
1189        Some(self.active_context_editor(cx)?.read(cx).context.clone())
1190    }
1191
1192    fn open_saved_context(
1193        &mut self,
1194        path: PathBuf,
1195        cx: &mut ViewContext<Self>,
1196    ) -> Task<Result<()>> {
1197        let existing_context = self.pane.read(cx).items().find_map(|item| {
1198            item.downcast::<ContextEditor>()
1199                .filter(|editor| editor.read(cx).context.read(cx).path() == Some(&path))
1200        });
1201        if let Some(existing_context) = existing_context {
1202            return cx.spawn(|this, mut cx| async move {
1203                this.update(&mut cx, |this, cx| this.show_context(existing_context, cx))
1204            });
1205        }
1206
1207        let context = self
1208            .context_store
1209            .update(cx, |store, cx| store.open_local_context(path.clone(), cx));
1210        let fs = self.fs.clone();
1211        let project = self.project.clone();
1212        let workspace = self.workspace.clone();
1213
1214        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
1215
1216        cx.spawn(|this, mut cx| async move {
1217            let context = context.await?;
1218            let assistant_panel = this.clone();
1219            this.update(&mut cx, |this, cx| {
1220                let editor = cx.new_view(|cx| {
1221                    ContextEditor::for_context(
1222                        context,
1223                        fs,
1224                        workspace,
1225                        project,
1226                        lsp_adapter_delegate,
1227                        assistant_panel,
1228                        cx,
1229                    )
1230                });
1231                this.show_context(editor, cx);
1232                anyhow::Ok(())
1233            })??;
1234            Ok(())
1235        })
1236    }
1237
1238    fn open_remote_context(
1239        &mut self,
1240        id: ContextId,
1241        cx: &mut ViewContext<Self>,
1242    ) -> Task<Result<View<ContextEditor>>> {
1243        let existing_context = self.pane.read(cx).items().find_map(|item| {
1244            item.downcast::<ContextEditor>()
1245                .filter(|editor| *editor.read(cx).context.read(cx).id() == id)
1246        });
1247        if let Some(existing_context) = existing_context {
1248            return cx.spawn(|this, mut cx| async move {
1249                this.update(&mut cx, |this, cx| {
1250                    this.show_context(existing_context.clone(), cx)
1251                })?;
1252                Ok(existing_context)
1253            });
1254        }
1255
1256        let context = self
1257            .context_store
1258            .update(cx, |store, cx| store.open_remote_context(id, cx));
1259        let fs = self.fs.clone();
1260        let workspace = self.workspace.clone();
1261        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
1262            .log_err()
1263            .flatten();
1264
1265        cx.spawn(|this, mut cx| async move {
1266            let context = context.await?;
1267            let assistant_panel = this.clone();
1268            this.update(&mut cx, |this, cx| {
1269                let editor = cx.new_view(|cx| {
1270                    ContextEditor::for_context(
1271                        context,
1272                        fs,
1273                        workspace,
1274                        this.project.clone(),
1275                        lsp_adapter_delegate,
1276                        assistant_panel,
1277                        cx,
1278                    )
1279                });
1280                this.show_context(editor.clone(), cx);
1281                anyhow::Ok(editor)
1282            })?
1283        })
1284    }
1285
1286    fn is_authenticated(&mut self, cx: &mut ViewContext<Self>) -> bool {
1287        LanguageModelRegistry::read_global(cx)
1288            .active_provider()
1289            .map_or(false, |provider| provider.is_authenticated(cx))
1290    }
1291
1292    fn authenticate(&mut self, cx: &mut ViewContext<Self>) -> Option<Task<Result<()>>> {
1293        LanguageModelRegistry::read_global(cx)
1294            .active_provider()
1295            .map_or(None, |provider| Some(provider.authenticate(cx)))
1296    }
1297}
1298
1299impl Render for AssistantPanel {
1300    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1301        let mut registrar = DivRegistrar::new(
1302            |panel, cx| {
1303                panel
1304                    .pane
1305                    .read(cx)
1306                    .toolbar()
1307                    .read(cx)
1308                    .item_of_type::<BufferSearchBar>()
1309            },
1310            cx,
1311        );
1312        BufferSearchBar::register(&mut registrar);
1313        let registrar = registrar.into_div();
1314
1315        v_flex()
1316            .key_context("AssistantPanel")
1317            .size_full()
1318            .on_action(cx.listener(|this, _: &NewContext, cx| {
1319                this.new_context(cx);
1320            }))
1321            .on_action(
1322                cx.listener(|this, _: &ShowConfiguration, cx| this.show_configuration_tab(cx)),
1323            )
1324            .on_action(cx.listener(AssistantPanel::deploy_history))
1325            .on_action(cx.listener(AssistantPanel::deploy_prompt_library))
1326            .on_action(cx.listener(AssistantPanel::toggle_model_selector))
1327            .child(registrar.size_full().child(self.pane.clone()))
1328            .into_any_element()
1329    }
1330}
1331
1332impl Panel for AssistantPanel {
1333    fn persistent_name() -> &'static str {
1334        "AssistantPanel"
1335    }
1336
1337    fn position(&self, cx: &WindowContext) -> DockPosition {
1338        match AssistantSettings::get_global(cx).dock {
1339            AssistantDockPosition::Left => DockPosition::Left,
1340            AssistantDockPosition::Bottom => DockPosition::Bottom,
1341            AssistantDockPosition::Right => DockPosition::Right,
1342        }
1343    }
1344
1345    fn position_is_valid(&self, _: DockPosition) -> bool {
1346        true
1347    }
1348
1349    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
1350        settings::update_settings_file::<AssistantSettings>(
1351            self.fs.clone(),
1352            cx,
1353            move |settings, _| {
1354                let dock = match position {
1355                    DockPosition::Left => AssistantDockPosition::Left,
1356                    DockPosition::Bottom => AssistantDockPosition::Bottom,
1357                    DockPosition::Right => AssistantDockPosition::Right,
1358                };
1359                settings.set_dock(dock);
1360            },
1361        );
1362    }
1363
1364    fn size(&self, cx: &WindowContext) -> Pixels {
1365        let settings = AssistantSettings::get_global(cx);
1366        match self.position(cx) {
1367            DockPosition::Left | DockPosition::Right => {
1368                self.width.unwrap_or(settings.default_width)
1369            }
1370            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1371        }
1372    }
1373
1374    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
1375        match self.position(cx) {
1376            DockPosition::Left | DockPosition::Right => self.width = size,
1377            DockPosition::Bottom => self.height = size,
1378        }
1379        cx.notify();
1380    }
1381
1382    fn is_zoomed(&self, cx: &WindowContext) -> bool {
1383        self.pane.read(cx).is_zoomed()
1384    }
1385
1386    fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
1387        self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
1388    }
1389
1390    fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
1391        if active {
1392            if self.pane.read(cx).items_len() == 0 {
1393                self.new_context(cx);
1394            }
1395
1396            self.ensure_authenticated(cx);
1397        }
1398    }
1399
1400    fn pane(&self) -> Option<View<Pane>> {
1401        Some(self.pane.clone())
1402    }
1403
1404    fn remote_id() -> Option<proto::PanelId> {
1405        Some(proto::PanelId::AssistantPanel)
1406    }
1407
1408    fn icon(&self, cx: &WindowContext) -> Option<IconName> {
1409        let settings = AssistantSettings::get_global(cx);
1410        if !settings.enabled || !settings.button {
1411            return None;
1412        }
1413
1414        Some(IconName::ZedAssistant)
1415    }
1416
1417    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
1418        Some("Assistant Panel")
1419    }
1420
1421    fn toggle_action(&self) -> Box<dyn Action> {
1422        Box::new(ToggleFocus)
1423    }
1424}
1425
1426impl EventEmitter<PanelEvent> for AssistantPanel {}
1427impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
1428
1429impl FocusableView for AssistantPanel {
1430    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
1431        self.pane.focus_handle(cx)
1432    }
1433}
1434
1435pub enum ContextEditorEvent {
1436    Edited,
1437    TabContentChanged,
1438}
1439
1440#[derive(Copy, Clone, Debug, PartialEq)]
1441struct ScrollPosition {
1442    offset_before_cursor: gpui::Point<f32>,
1443    cursor: Anchor,
1444}
1445
1446struct PatchViewState {
1447    footer_block_id: CustomBlockId,
1448    crease_id: CreaseId,
1449    editor: Option<PatchEditorState>,
1450    update_task: Option<Task<()>>,
1451}
1452
1453struct PatchEditorState {
1454    editor: WeakView<ProposedChangesEditor>,
1455    opened_patch: AssistantPatch,
1456}
1457
1458type MessageHeader = MessageMetadata;
1459
1460#[derive(Clone)]
1461enum AssistError {
1462    FileRequired,
1463    PaymentRequired,
1464    MaxMonthlySpendReached,
1465    Message(SharedString),
1466}
1467
1468pub struct ContextEditor {
1469    context: Model<Context>,
1470    fs: Arc<dyn Fs>,
1471    workspace: WeakView<Workspace>,
1472    project: Model<Project>,
1473    lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1474    editor: View<Editor>,
1475    blocks: HashMap<MessageId, (MessageHeader, CustomBlockId)>,
1476    image_blocks: HashSet<CustomBlockId>,
1477    scroll_position: Option<ScrollPosition>,
1478    remote_id: Option<workspace::ViewId>,
1479    pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
1480    pending_slash_command_blocks: HashMap<Range<language::Anchor>, CustomBlockId>,
1481    pending_tool_use_creases: HashMap<Range<language::Anchor>, CreaseId>,
1482    _subscriptions: Vec<Subscription>,
1483    patches: HashMap<Range<language::Anchor>, PatchViewState>,
1484    active_patch: Option<Range<language::Anchor>>,
1485    assistant_panel: WeakView<AssistantPanel>,
1486    last_error: Option<AssistError>,
1487    show_accept_terms: bool,
1488    pub(crate) slash_menu_handle:
1489        PopoverMenuHandle<Picker<slash_command_picker::SlashCommandDelegate>>,
1490    // dragged_file_worktrees is used to keep references to worktrees that were added
1491    // when the user drag/dropped an external file onto the context editor. Since
1492    // the worktree is not part of the project panel, it would be dropped as soon as
1493    // the file is opened. In order to keep the worktree alive for the duration of the
1494    // context editor, we keep a reference here.
1495    dragged_file_worktrees: Vec<Model<Worktree>>,
1496}
1497
1498const DEFAULT_TAB_TITLE: &str = "New Chat";
1499const MAX_TAB_TITLE_LEN: usize = 16;
1500
1501impl ContextEditor {
1502    fn for_context(
1503        context: Model<Context>,
1504        fs: Arc<dyn Fs>,
1505        workspace: WeakView<Workspace>,
1506        project: Model<Project>,
1507        lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1508        assistant_panel: WeakView<AssistantPanel>,
1509        cx: &mut ViewContext<Self>,
1510    ) -> Self {
1511        let completion_provider = SlashCommandCompletionProvider::new(
1512            Some(cx.view().downgrade()),
1513            Some(workspace.clone()),
1514        );
1515
1516        let editor = cx.new_view(|cx| {
1517            let mut editor = Editor::for_buffer(context.read(cx).buffer().clone(), None, cx);
1518            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
1519            editor.set_show_line_numbers(false, cx);
1520            editor.set_show_git_diff_gutter(false, cx);
1521            editor.set_show_code_actions(false, cx);
1522            editor.set_show_runnables(false, cx);
1523            editor.set_show_wrap_guides(false, cx);
1524            editor.set_show_indent_guides(false, cx);
1525            editor.set_completion_provider(Some(Box::new(completion_provider)));
1526            editor.set_collaboration_hub(Box::new(project.clone()));
1527            editor
1528        });
1529
1530        let _subscriptions = vec![
1531            cx.observe(&context, |_, _, cx| cx.notify()),
1532            cx.subscribe(&context, Self::handle_context_event),
1533            cx.subscribe(&editor, Self::handle_editor_event),
1534            cx.subscribe(&editor, Self::handle_editor_search_event),
1535        ];
1536
1537        let sections = context.read(cx).slash_command_output_sections().to_vec();
1538        let patch_ranges = context.read(cx).patch_ranges().collect::<Vec<_>>();
1539        let mut this = Self {
1540            context,
1541            editor,
1542            lsp_adapter_delegate,
1543            blocks: Default::default(),
1544            image_blocks: Default::default(),
1545            scroll_position: None,
1546            remote_id: None,
1547            fs,
1548            workspace,
1549            project,
1550            pending_slash_command_creases: HashMap::default(),
1551            pending_slash_command_blocks: HashMap::default(),
1552            pending_tool_use_creases: HashMap::default(),
1553            _subscriptions,
1554            patches: HashMap::default(),
1555            active_patch: None,
1556            assistant_panel,
1557            last_error: None,
1558            show_accept_terms: false,
1559            slash_menu_handle: Default::default(),
1560            dragged_file_worktrees: Vec::new(),
1561        };
1562        this.update_message_headers(cx);
1563        this.update_image_blocks(cx);
1564        this.insert_slash_command_output_sections(sections, false, cx);
1565        this.patches_updated(&Vec::new(), &patch_ranges, cx);
1566        this
1567    }
1568
1569    fn insert_default_prompt(&mut self, cx: &mut ViewContext<Self>) {
1570        let command_name = DefaultSlashCommand.name();
1571        self.editor.update(cx, |editor, cx| {
1572            editor.insert(&format!("/{command_name}\n\n"), cx)
1573        });
1574        let command = self.context.update(cx, |context, cx| {
1575            context.reparse(cx);
1576            context.pending_slash_commands()[0].clone()
1577        });
1578        self.run_command(
1579            command.source_range,
1580            &command.name,
1581            &command.arguments,
1582            false,
1583            false,
1584            self.workspace.clone(),
1585            cx,
1586        );
1587    }
1588
1589    fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
1590        self.send_to_model(RequestType::Chat, cx);
1591    }
1592
1593    fn edit(&mut self, _: &Edit, cx: &mut ViewContext<Self>) {
1594        self.send_to_model(RequestType::SuggestEdits, cx);
1595    }
1596
1597    fn focus_active_patch(&mut self, cx: &mut ViewContext<Self>) -> bool {
1598        if let Some((_range, patch)) = self.active_patch() {
1599            if let Some(editor) = patch
1600                .editor
1601                .as_ref()
1602                .and_then(|state| state.editor.upgrade())
1603            {
1604                cx.focus_view(&editor);
1605                return true;
1606            }
1607        }
1608
1609        false
1610    }
1611
1612    fn send_to_model(&mut self, request_type: RequestType, cx: &mut ViewContext<Self>) {
1613        let provider = LanguageModelRegistry::read_global(cx).active_provider();
1614        if provider
1615            .as_ref()
1616            .map_or(false, |provider| provider.must_accept_terms(cx))
1617        {
1618            self.show_accept_terms = true;
1619            cx.notify();
1620            return;
1621        }
1622
1623        if self.focus_active_patch(cx) {
1624            return;
1625        }
1626
1627        self.last_error = None;
1628
1629        if request_type == RequestType::SuggestEdits && !self.context.read(cx).contains_files(cx) {
1630            self.last_error = Some(AssistError::FileRequired);
1631            cx.notify();
1632        } else if let Some(user_message) = self
1633            .context
1634            .update(cx, |context, cx| context.assist(request_type, cx))
1635        {
1636            let new_selection = {
1637                let cursor = user_message
1638                    .start
1639                    .to_offset(self.context.read(cx).buffer().read(cx));
1640                cursor..cursor
1641            };
1642            self.editor.update(cx, |editor, cx| {
1643                editor.change_selections(
1644                    Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
1645                    cx,
1646                    |selections| selections.select_ranges([new_selection]),
1647                );
1648            });
1649            // Avoid scrolling to the new cursor position so the assistant's output is stable.
1650            cx.defer(|this, _| this.scroll_position = None);
1651        }
1652
1653        cx.notify();
1654    }
1655
1656    fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
1657        self.last_error = None;
1658
1659        if self
1660            .context
1661            .update(cx, |context, cx| context.cancel_last_assist(cx))
1662        {
1663            return;
1664        }
1665
1666        cx.propagate();
1667    }
1668
1669    fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
1670        let cursors = self.cursors(cx);
1671        self.context.update(cx, |context, cx| {
1672            let messages = context
1673                .messages_for_offsets(cursors, cx)
1674                .into_iter()
1675                .map(|message| message.id)
1676                .collect();
1677            context.cycle_message_roles(messages, cx)
1678        });
1679    }
1680
1681    fn cursors(&self, cx: &mut WindowContext) -> Vec<usize> {
1682        let selections = self
1683            .editor
1684            .update(cx, |editor, cx| editor.selections.all::<usize>(cx));
1685        selections
1686            .into_iter()
1687            .map(|selection| selection.head())
1688            .collect()
1689    }
1690
1691    pub fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
1692        if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
1693            self.editor.update(cx, |editor, cx| {
1694                editor.transact(cx, |editor, cx| {
1695                    editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel());
1696                    let snapshot = editor.buffer().read(cx).snapshot(cx);
1697                    let newest_cursor = editor.selections.newest::<Point>(cx).head();
1698                    if newest_cursor.column > 0
1699                        || snapshot
1700                            .chars_at(newest_cursor)
1701                            .next()
1702                            .map_or(false, |ch| ch != '\n')
1703                    {
1704                        editor.move_to_end_of_line(
1705                            &MoveToEndOfLine {
1706                                stop_at_soft_wraps: false,
1707                            },
1708                            cx,
1709                        );
1710                        editor.newline(&Newline, cx);
1711                    }
1712
1713                    editor.insert(&format!("/{name}"), cx);
1714                    if command.accepts_arguments() {
1715                        editor.insert(" ", cx);
1716                        editor.show_completions(&ShowCompletions::default(), cx);
1717                    }
1718                });
1719            });
1720            if !command.requires_argument() {
1721                self.confirm_command(&ConfirmCommand, cx);
1722            }
1723        }
1724    }
1725
1726    pub fn confirm_command(&mut self, _: &ConfirmCommand, cx: &mut ViewContext<Self>) {
1727        if self.editor.read(cx).has_active_completions_menu() {
1728            return;
1729        }
1730
1731        let selections = self.editor.read(cx).selections.disjoint_anchors();
1732        let mut commands_by_range = HashMap::default();
1733        let workspace = self.workspace.clone();
1734        self.context.update(cx, |context, cx| {
1735            context.reparse(cx);
1736            for selection in selections.iter() {
1737                if let Some(command) =
1738                    context.pending_command_for_position(selection.head().text_anchor, cx)
1739                {
1740                    commands_by_range
1741                        .entry(command.source_range.clone())
1742                        .or_insert_with(|| command.clone());
1743                }
1744            }
1745        });
1746
1747        if commands_by_range.is_empty() {
1748            cx.propagate();
1749        } else {
1750            for command in commands_by_range.into_values() {
1751                self.run_command(
1752                    command.source_range,
1753                    &command.name,
1754                    &command.arguments,
1755                    true,
1756                    false,
1757                    workspace.clone(),
1758                    cx,
1759                );
1760            }
1761            cx.stop_propagation();
1762        }
1763    }
1764
1765    #[allow(clippy::too_many_arguments)]
1766    pub fn run_command(
1767        &mut self,
1768        command_range: Range<language::Anchor>,
1769        name: &str,
1770        arguments: &[String],
1771        ensure_trailing_newline: bool,
1772        expand_result: bool,
1773        workspace: WeakView<Workspace>,
1774        cx: &mut ViewContext<Self>,
1775    ) {
1776        if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
1777            let context = self.context.read(cx);
1778            let sections = context
1779                .slash_command_output_sections()
1780                .into_iter()
1781                .filter(|section| section.is_valid(context.buffer().read(cx)))
1782                .cloned()
1783                .collect::<Vec<_>>();
1784            let snapshot = context.buffer().read(cx).snapshot();
1785            let output = command.run(
1786                arguments,
1787                &sections,
1788                snapshot,
1789                workspace,
1790                self.lsp_adapter_delegate.clone(),
1791                cx,
1792            );
1793            self.context.update(cx, |context, cx| {
1794                context.insert_command_output(
1795                    command_range,
1796                    output,
1797                    ensure_trailing_newline,
1798                    expand_result,
1799                    cx,
1800                )
1801            });
1802        }
1803    }
1804
1805    fn handle_context_event(
1806        &mut self,
1807        _: Model<Context>,
1808        event: &ContextEvent,
1809        cx: &mut ViewContext<Self>,
1810    ) {
1811        let context_editor = cx.view().downgrade();
1812
1813        match event {
1814            ContextEvent::MessagesEdited => {
1815                self.update_message_headers(cx);
1816                self.update_image_blocks(cx);
1817                self.context.update(cx, |context, cx| {
1818                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
1819                });
1820            }
1821            ContextEvent::SummaryChanged => {
1822                cx.emit(EditorEvent::TitleChanged);
1823                self.context.update(cx, |context, cx| {
1824                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
1825                });
1826            }
1827            ContextEvent::StreamedCompletion => {
1828                self.editor.update(cx, |editor, cx| {
1829                    if let Some(scroll_position) = self.scroll_position {
1830                        let snapshot = editor.snapshot(cx);
1831                        let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
1832                        let scroll_top =
1833                            cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
1834                        editor.set_scroll_position(
1835                            point(scroll_position.offset_before_cursor.x, scroll_top),
1836                            cx,
1837                        );
1838                    }
1839
1840                    let new_tool_uses = self
1841                        .context
1842                        .read(cx)
1843                        .pending_tool_uses()
1844                        .into_iter()
1845                        .filter(|tool_use| {
1846                            !self
1847                                .pending_tool_use_creases
1848                                .contains_key(&tool_use.source_range)
1849                        })
1850                        .cloned()
1851                        .collect::<Vec<_>>();
1852
1853                    let buffer = editor.buffer().read(cx).snapshot(cx);
1854                    let (excerpt_id, _buffer_id, _) = buffer.as_singleton().unwrap();
1855                    let excerpt_id = *excerpt_id;
1856
1857                    let mut buffer_rows_to_fold = BTreeSet::new();
1858
1859                    let creases = new_tool_uses
1860                        .iter()
1861                        .map(|tool_use| {
1862                            let placeholder = FoldPlaceholder {
1863                                render: render_fold_icon_button(
1864                                    cx.view().downgrade(),
1865                                    IconName::PocketKnife,
1866                                    tool_use.name.clone().into(),
1867                                ),
1868                                constrain_width: false,
1869                                merge_adjacent: false,
1870                            };
1871                            let render_trailer =
1872                                move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
1873
1874                            let start = buffer
1875                                .anchor_in_excerpt(excerpt_id, tool_use.source_range.start)
1876                                .unwrap();
1877                            let end = buffer
1878                                .anchor_in_excerpt(excerpt_id, tool_use.source_range.end)
1879                                .unwrap();
1880
1881                            let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
1882                            buffer_rows_to_fold.insert(buffer_row);
1883
1884                            self.context.update(cx, |context, cx| {
1885                                context.insert_content(
1886                                    Content::ToolUse {
1887                                        range: tool_use.source_range.clone(),
1888                                        tool_use: LanguageModelToolUse {
1889                                            id: tool_use.id.to_string(),
1890                                            name: tool_use.name.clone(),
1891                                            input: tool_use.input.clone(),
1892                                        },
1893                                    },
1894                                    cx,
1895                                );
1896                            });
1897
1898                            Crease::new(
1899                                start..end,
1900                                placeholder,
1901                                fold_toggle("tool-use"),
1902                                render_trailer,
1903                            )
1904                        })
1905                        .collect::<Vec<_>>();
1906
1907                    let crease_ids = editor.insert_creases(creases, cx);
1908
1909                    for buffer_row in buffer_rows_to_fold.into_iter().rev() {
1910                        editor.fold_at(&FoldAt { buffer_row }, cx);
1911                    }
1912
1913                    self.pending_tool_use_creases.extend(
1914                        new_tool_uses
1915                            .iter()
1916                            .map(|tool_use| tool_use.source_range.clone())
1917                            .zip(crease_ids),
1918                    );
1919                });
1920            }
1921            ContextEvent::PatchesUpdated { removed, updated } => {
1922                self.patches_updated(removed, updated, cx);
1923            }
1924            ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
1925                self.editor.update(cx, |editor, cx| {
1926                    let buffer = editor.buffer().read(cx).snapshot(cx);
1927                    let (excerpt_id, buffer_id, _) = buffer.as_singleton().unwrap();
1928                    let excerpt_id = *excerpt_id;
1929
1930                    editor.remove_creases(
1931                        removed
1932                            .iter()
1933                            .filter_map(|range| self.pending_slash_command_creases.remove(range)),
1934                        cx,
1935                    );
1936
1937                    editor.remove_blocks(
1938                        HashSet::from_iter(
1939                            removed.iter().filter_map(|range| {
1940                                self.pending_slash_command_blocks.remove(range)
1941                            }),
1942                        ),
1943                        None,
1944                        cx,
1945                    );
1946
1947                    let crease_ids = editor.insert_creases(
1948                        updated.iter().map(|command| {
1949                            let workspace = self.workspace.clone();
1950                            let confirm_command = Arc::new({
1951                                let context_editor = context_editor.clone();
1952                                let command = command.clone();
1953                                move |cx: &mut WindowContext| {
1954                                    context_editor
1955                                        .update(cx, |context_editor, cx| {
1956                                            context_editor.run_command(
1957                                                command.source_range.clone(),
1958                                                &command.name,
1959                                                &command.arguments,
1960                                                false,
1961                                                false,
1962                                                workspace.clone(),
1963                                                cx,
1964                                            );
1965                                        })
1966                                        .ok();
1967                                }
1968                            });
1969                            let placeholder = FoldPlaceholder {
1970                                render: Arc::new(move |_, _, _| Empty.into_any()),
1971                                constrain_width: false,
1972                                merge_adjacent: false,
1973                            };
1974                            let render_toggle = {
1975                                let confirm_command = confirm_command.clone();
1976                                let command = command.clone();
1977                                move |row, _, _, _cx: &mut WindowContext| {
1978                                    render_pending_slash_command_gutter_decoration(
1979                                        row,
1980                                        &command.status,
1981                                        confirm_command.clone(),
1982                                    )
1983                                }
1984                            };
1985                            let render_trailer = {
1986                                let command = command.clone();
1987                                move |row, _unfold, cx: &mut WindowContext| {
1988                                    // TODO: In the future we should investigate how we can expose
1989                                    // this as a hook on the `SlashCommand` trait so that we don't
1990                                    // need to special-case it here.
1991                                    if command.name == DocsSlashCommand::NAME {
1992                                        return render_docs_slash_command_trailer(
1993                                            row,
1994                                            command.clone(),
1995                                            cx,
1996                                        );
1997                                    }
1998
1999                                    Empty.into_any()
2000                                }
2001                            };
2002
2003                            let start = buffer
2004                                .anchor_in_excerpt(excerpt_id, command.source_range.start)
2005                                .unwrap();
2006                            let end = buffer
2007                                .anchor_in_excerpt(excerpt_id, command.source_range.end)
2008                                .unwrap();
2009                            Crease::new(start..end, placeholder, render_toggle, render_trailer)
2010                        }),
2011                        cx,
2012                    );
2013
2014                    let block_ids = editor.insert_blocks(
2015                        updated
2016                            .iter()
2017                            .filter_map(|command| match &command.status {
2018                                PendingSlashCommandStatus::Error(error) => {
2019                                    Some((command, error.clone()))
2020                                }
2021                                _ => None,
2022                            })
2023                            .map(|(command, error_message)| BlockProperties {
2024                                style: BlockStyle::Fixed,
2025                                height: 1,
2026                                placement: BlockPlacement::Below(Anchor {
2027                                    buffer_id: Some(buffer_id),
2028                                    excerpt_id,
2029                                    text_anchor: command.source_range.start,
2030                                }),
2031                                render: slash_command_error_block_renderer(error_message),
2032                                priority: 0,
2033                            }),
2034                        None,
2035                        cx,
2036                    );
2037
2038                    self.pending_slash_command_creases.extend(
2039                        updated
2040                            .iter()
2041                            .map(|command| command.source_range.clone())
2042                            .zip(crease_ids),
2043                    );
2044
2045                    self.pending_slash_command_blocks.extend(
2046                        updated
2047                            .iter()
2048                            .map(|command| command.source_range.clone())
2049                            .zip(block_ids),
2050                    );
2051                })
2052            }
2053            ContextEvent::SlashCommandFinished {
2054                output_range,
2055                sections,
2056                run_commands_in_output,
2057                expand_result,
2058            } => {
2059                self.insert_slash_command_output_sections(
2060                    sections.iter().cloned(),
2061                    *expand_result,
2062                    cx,
2063                );
2064
2065                if *run_commands_in_output {
2066                    let commands = self.context.update(cx, |context, cx| {
2067                        context.reparse(cx);
2068                        context
2069                            .pending_commands_for_range(output_range.clone(), cx)
2070                            .to_vec()
2071                    });
2072
2073                    for command in commands {
2074                        self.run_command(
2075                            command.source_range,
2076                            &command.name,
2077                            &command.arguments,
2078                            false,
2079                            false,
2080                            self.workspace.clone(),
2081                            cx,
2082                        );
2083                    }
2084                }
2085            }
2086            ContextEvent::UsePendingTools => {
2087                let pending_tool_uses = self
2088                    .context
2089                    .read(cx)
2090                    .pending_tool_uses()
2091                    .into_iter()
2092                    .filter(|tool_use| tool_use.status.is_idle())
2093                    .cloned()
2094                    .collect::<Vec<_>>();
2095
2096                for tool_use in pending_tool_uses {
2097                    let tool_registry = ToolRegistry::global(cx);
2098                    if let Some(tool) = tool_registry.tool(&tool_use.name) {
2099                        let task = tool.run(tool_use.input, self.workspace.clone(), cx);
2100
2101                        self.context.update(cx, |context, cx| {
2102                            context.insert_tool_output(tool_use.id.clone(), task, cx);
2103                        });
2104                    }
2105                }
2106            }
2107            ContextEvent::ToolFinished {
2108                tool_use_id,
2109                output_range,
2110            } => {
2111                self.editor.update(cx, |editor, cx| {
2112                    let buffer = editor.buffer().read(cx).snapshot(cx);
2113                    let (excerpt_id, _buffer_id, _) = buffer.as_singleton().unwrap();
2114                    let excerpt_id = *excerpt_id;
2115
2116                    let placeholder = FoldPlaceholder {
2117                        render: render_fold_icon_button(
2118                            cx.view().downgrade(),
2119                            IconName::PocketKnife,
2120                            format!("Tool Result: {tool_use_id}").into(),
2121                        ),
2122                        constrain_width: false,
2123                        merge_adjacent: false,
2124                    };
2125                    let render_trailer =
2126                        move |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
2127
2128                    let start = buffer
2129                        .anchor_in_excerpt(excerpt_id, output_range.start)
2130                        .unwrap();
2131                    let end = buffer
2132                        .anchor_in_excerpt(excerpt_id, output_range.end)
2133                        .unwrap();
2134
2135                    let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2136
2137                    let crease = Crease::new(
2138                        start..end,
2139                        placeholder,
2140                        fold_toggle("tool-use"),
2141                        render_trailer,
2142                    );
2143
2144                    editor.insert_creases([crease], cx);
2145                    editor.fold_at(&FoldAt { buffer_row }, cx);
2146                });
2147            }
2148            ContextEvent::Operation(_) => {}
2149            ContextEvent::ShowAssistError(error_message) => {
2150                self.last_error = Some(AssistError::Message(error_message.clone()));
2151            }
2152            ContextEvent::ShowPaymentRequiredError => {
2153                self.last_error = Some(AssistError::PaymentRequired);
2154            }
2155            ContextEvent::ShowMaxMonthlySpendReachedError => {
2156                self.last_error = Some(AssistError::MaxMonthlySpendReached);
2157            }
2158        }
2159    }
2160
2161    fn patches_updated(
2162        &mut self,
2163        removed: &Vec<Range<text::Anchor>>,
2164        updated: &Vec<Range<text::Anchor>>,
2165        cx: &mut ViewContext<ContextEditor>,
2166    ) {
2167        let this = cx.view().downgrade();
2168        let mut removed_crease_ids = Vec::new();
2169        let mut removed_block_ids = HashSet::default();
2170        let mut editors_to_close = Vec::new();
2171        for range in removed {
2172            if let Some(state) = self.patches.remove(range) {
2173                editors_to_close.extend(state.editor.and_then(|state| state.editor.upgrade()));
2174                removed_block_ids.insert(state.footer_block_id);
2175                removed_crease_ids.push(state.crease_id);
2176            }
2177        }
2178
2179        self.editor.update(cx, |editor, cx| {
2180            let snapshot = editor.snapshot(cx);
2181            let multibuffer = &snapshot.buffer_snapshot;
2182            let (&excerpt_id, _, _) = multibuffer.as_singleton().unwrap();
2183
2184            let mut replaced_blocks = HashMap::default();
2185            for range in updated {
2186                let Some(patch) = self.context.read(cx).patch_for_range(&range, cx).cloned() else {
2187                    continue;
2188                };
2189
2190                let path_count = patch.path_count();
2191                let patch_start = multibuffer
2192                    .anchor_in_excerpt(excerpt_id, patch.range.start)
2193                    .unwrap();
2194                let patch_end = multibuffer
2195                    .anchor_in_excerpt(excerpt_id, patch.range.end)
2196                    .unwrap();
2197                let render_block: RenderBlock = Box::new({
2198                    let this = this.clone();
2199                    let patch_range = range.clone();
2200                    move |cx: &mut BlockContext<'_, '_>| {
2201                        let max_width = cx.max_width;
2202                        let gutter_width = cx.gutter_dimensions.full_width();
2203                        let block_id = cx.block_id;
2204                        this.update(&mut **cx, |this, cx| {
2205                            this.render_patch_footer(
2206                                patch_range.clone(),
2207                                max_width,
2208                                gutter_width,
2209                                block_id,
2210                                cx,
2211                            )
2212                        })
2213                        .ok()
2214                        .flatten()
2215                        .unwrap_or_else(|| Empty.into_any())
2216                    }
2217                });
2218
2219                let header_placeholder = FoldPlaceholder {
2220                    render: {
2221                        let this = this.clone();
2222                        let patch_range = range.clone();
2223                        Arc::new(move |fold_id, _range, cx| {
2224                            this.update(cx, |this, cx| {
2225                                this.render_patch_header(patch_range.clone(), fold_id, cx)
2226                            })
2227                            .ok()
2228                            .flatten()
2229                            .unwrap_or_else(|| Empty.into_any())
2230                        })
2231                    },
2232                    constrain_width: false,
2233                    merge_adjacent: false,
2234                };
2235
2236                let should_refold;
2237                if let Some(state) = self.patches.get_mut(&range) {
2238                    replaced_blocks.insert(state.footer_block_id, render_block);
2239                    if let Some(editor_state) = &state.editor {
2240                        if editor_state.opened_patch != patch {
2241                            state.update_task = Some({
2242                                let this = this.clone();
2243                                cx.spawn(|_, cx| async move {
2244                                    Self::update_patch_editor(this.clone(), patch, cx)
2245                                        .await
2246                                        .log_err();
2247                                })
2248                            });
2249                        }
2250                    }
2251
2252                    should_refold =
2253                        snapshot.intersects_fold(patch_start.to_offset(&snapshot.buffer_snapshot));
2254                } else {
2255                    let block_ids = editor.insert_blocks(
2256                        [BlockProperties {
2257                            height: path_count as u32 + 1,
2258                            style: BlockStyle::Flex,
2259                            render: render_block,
2260                            placement: BlockPlacement::Below(patch_start),
2261                            priority: 0,
2262                        }],
2263                        None,
2264                        cx,
2265                    );
2266
2267                    let new_crease_ids = editor.insert_creases(
2268                        [Crease::new(
2269                            patch_start..patch_end,
2270                            header_placeholder.clone(),
2271                            fold_toggle("patch-header"),
2272                            |_, _, _| Empty.into_any_element(),
2273                        )],
2274                        cx,
2275                    );
2276
2277                    self.patches.insert(
2278                        range.clone(),
2279                        PatchViewState {
2280                            footer_block_id: block_ids[0],
2281                            crease_id: new_crease_ids[0],
2282                            editor: None,
2283                            update_task: None,
2284                        },
2285                    );
2286
2287                    should_refold = true;
2288                }
2289
2290                if should_refold {
2291                    editor.unfold_ranges([patch_start..patch_end], true, false, cx);
2292                    editor.fold_ranges([(patch_start..patch_end, header_placeholder)], false, cx);
2293                }
2294            }
2295
2296            editor.remove_creases(removed_crease_ids, cx);
2297            editor.remove_blocks(removed_block_ids, None, cx);
2298            editor.replace_blocks(replaced_blocks, None, cx);
2299        });
2300
2301        for editor in editors_to_close {
2302            self.close_patch_editor(editor, cx);
2303        }
2304
2305        self.update_active_patch(cx);
2306    }
2307
2308    fn insert_slash_command_output_sections(
2309        &mut self,
2310        sections: impl IntoIterator<Item = SlashCommandOutputSection<language::Anchor>>,
2311        expand_result: bool,
2312        cx: &mut ViewContext<Self>,
2313    ) {
2314        self.editor.update(cx, |editor, cx| {
2315            let buffer = editor.buffer().read(cx).snapshot(cx);
2316            let excerpt_id = *buffer.as_singleton().unwrap().0;
2317            let mut buffer_rows_to_fold = BTreeSet::new();
2318            let mut creases = Vec::new();
2319            for section in sections {
2320                let start = buffer
2321                    .anchor_in_excerpt(excerpt_id, section.range.start)
2322                    .unwrap();
2323                let end = buffer
2324                    .anchor_in_excerpt(excerpt_id, section.range.end)
2325                    .unwrap();
2326                let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2327                buffer_rows_to_fold.insert(buffer_row);
2328                creases.push(
2329                    Crease::new(
2330                        start..end,
2331                        FoldPlaceholder {
2332                            render: render_fold_icon_button(
2333                                cx.view().downgrade(),
2334                                section.icon,
2335                                section.label.clone(),
2336                            ),
2337                            constrain_width: false,
2338                            merge_adjacent: false,
2339                        },
2340                        render_slash_command_output_toggle,
2341                        |_, _, _| Empty.into_any_element(),
2342                    )
2343                    .with_metadata(CreaseMetadata {
2344                        icon: section.icon,
2345                        label: section.label,
2346                    }),
2347                );
2348            }
2349
2350            editor.insert_creases(creases, cx);
2351
2352            if expand_result {
2353                buffer_rows_to_fold.clear();
2354            }
2355            for buffer_row in buffer_rows_to_fold.into_iter().rev() {
2356                editor.fold_at(&FoldAt { buffer_row }, cx);
2357            }
2358        });
2359    }
2360
2361    fn handle_editor_event(
2362        &mut self,
2363        _: View<Editor>,
2364        event: &EditorEvent,
2365        cx: &mut ViewContext<Self>,
2366    ) {
2367        match event {
2368            EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
2369                let cursor_scroll_position = self.cursor_scroll_position(cx);
2370                if *autoscroll {
2371                    self.scroll_position = cursor_scroll_position;
2372                } else if self.scroll_position != cursor_scroll_position {
2373                    self.scroll_position = None;
2374                }
2375            }
2376            EditorEvent::SelectionsChanged { .. } => {
2377                self.scroll_position = self.cursor_scroll_position(cx);
2378                self.update_active_patch(cx);
2379            }
2380            _ => {}
2381        }
2382        cx.emit(event.clone());
2383    }
2384
2385    fn active_patch(&self) -> Option<(Range<text::Anchor>, &PatchViewState)> {
2386        let patch = self.active_patch.as_ref()?;
2387        Some((patch.clone(), self.patches.get(&patch)?))
2388    }
2389
2390    fn update_active_patch(&mut self, cx: &mut ViewContext<Self>) {
2391        let newest_cursor = self.editor.update(cx, |editor, cx| {
2392            editor.selections.newest::<Point>(cx).head()
2393        });
2394        let context = self.context.read(cx);
2395
2396        let new_patch = context.patch_containing(newest_cursor, cx).cloned();
2397
2398        if new_patch.as_ref().map(|p| &p.range) == self.active_patch.as_ref() {
2399            return;
2400        }
2401
2402        if let Some(old_patch_range) = self.active_patch.take() {
2403            if let Some(patch_state) = self.patches.get_mut(&old_patch_range) {
2404                if let Some(state) = patch_state.editor.take() {
2405                    if let Some(editor) = state.editor.upgrade() {
2406                        self.close_patch_editor(editor, cx);
2407                    }
2408                }
2409            }
2410        }
2411
2412        if let Some(new_patch) = new_patch {
2413            self.active_patch = Some(new_patch.range.clone());
2414
2415            if let Some(patch_state) = self.patches.get_mut(&new_patch.range) {
2416                let mut editor = None;
2417                if let Some(state) = &patch_state.editor {
2418                    if let Some(opened_editor) = state.editor.upgrade() {
2419                        editor = Some(opened_editor);
2420                    }
2421                }
2422
2423                if let Some(editor) = editor {
2424                    self.workspace
2425                        .update(cx, |workspace, cx| {
2426                            workspace.activate_item(&editor, true, false, cx);
2427                        })
2428                        .ok();
2429                } else {
2430                    patch_state.update_task = Some(cx.spawn(move |this, cx| async move {
2431                        Self::open_patch_editor(this, new_patch, cx).await.log_err();
2432                    }));
2433                }
2434            }
2435        }
2436    }
2437
2438    fn close_patch_editor(
2439        &mut self,
2440        editor: View<ProposedChangesEditor>,
2441        cx: &mut ViewContext<ContextEditor>,
2442    ) {
2443        self.workspace
2444            .update(cx, |workspace, cx| {
2445                if let Some(pane) = workspace.pane_for(&editor) {
2446                    pane.update(cx, |pane, cx| {
2447                        let item_id = editor.entity_id();
2448                        if !editor.read(cx).focus_handle(cx).is_focused(cx) {
2449                            pane.close_item_by_id(item_id, SaveIntent::Skip, cx)
2450                                .detach_and_log_err(cx);
2451                        }
2452                    });
2453                }
2454            })
2455            .ok();
2456    }
2457
2458    async fn open_patch_editor(
2459        this: WeakView<Self>,
2460        patch: AssistantPatch,
2461        mut cx: AsyncWindowContext,
2462    ) -> Result<()> {
2463        let project = this.update(&mut cx, |this, _| this.project.clone())?;
2464        let resolved_patch = patch.resolve(project.clone(), &mut cx).await;
2465
2466        let editor = cx.new_view(|cx| {
2467            let editor = ProposedChangesEditor::new(
2468                patch.title.clone(),
2469                resolved_patch
2470                    .edit_groups
2471                    .iter()
2472                    .map(|(buffer, groups)| ProposedChangeLocation {
2473                        buffer: buffer.clone(),
2474                        ranges: groups
2475                            .iter()
2476                            .map(|group| group.context_range.clone())
2477                            .collect(),
2478                    })
2479                    .collect(),
2480                Some(project.clone()),
2481                cx,
2482            );
2483            resolved_patch.apply(&editor, cx);
2484            editor
2485        })?;
2486
2487        this.update(&mut cx, |this, cx| {
2488            if let Some(patch_state) = this.patches.get_mut(&patch.range) {
2489                patch_state.editor = Some(PatchEditorState {
2490                    editor: editor.downgrade(),
2491                    opened_patch: patch,
2492                });
2493                patch_state.update_task.take();
2494            }
2495
2496            this.workspace
2497                .update(cx, |workspace, cx| {
2498                    workspace.add_item_to_active_pane(Box::new(editor.clone()), None, false, cx)
2499                })
2500                .log_err();
2501        })?;
2502
2503        Ok(())
2504    }
2505
2506    async fn update_patch_editor(
2507        this: WeakView<Self>,
2508        patch: AssistantPatch,
2509        mut cx: AsyncWindowContext,
2510    ) -> Result<()> {
2511        let project = this.update(&mut cx, |this, _| this.project.clone())?;
2512        let resolved_patch = patch.resolve(project.clone(), &mut cx).await;
2513        this.update(&mut cx, |this, cx| {
2514            let patch_state = this.patches.get_mut(&patch.range)?;
2515
2516            let locations = resolved_patch
2517                .edit_groups
2518                .iter()
2519                .map(|(buffer, groups)| ProposedChangeLocation {
2520                    buffer: buffer.clone(),
2521                    ranges: groups
2522                        .iter()
2523                        .map(|group| group.context_range.clone())
2524                        .collect(),
2525                })
2526                .collect();
2527
2528            if let Some(state) = &mut patch_state.editor {
2529                if let Some(editor) = state.editor.upgrade() {
2530                    editor.update(cx, |editor, cx| {
2531                        editor.set_title(patch.title.clone(), cx);
2532                        editor.reset_locations(locations, cx);
2533                        resolved_patch.apply(editor, cx);
2534                    });
2535
2536                    state.opened_patch = patch;
2537                } else {
2538                    patch_state.editor.take();
2539                }
2540            }
2541            patch_state.update_task.take();
2542
2543            Some(())
2544        })?;
2545        Ok(())
2546    }
2547
2548    fn handle_editor_search_event(
2549        &mut self,
2550        _: View<Editor>,
2551        event: &SearchEvent,
2552        cx: &mut ViewContext<Self>,
2553    ) {
2554        cx.emit(event.clone());
2555    }
2556
2557    fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
2558        self.editor.update(cx, |editor, cx| {
2559            let snapshot = editor.snapshot(cx);
2560            let cursor = editor.selections.newest_anchor().head();
2561            let cursor_row = cursor
2562                .to_display_point(&snapshot.display_snapshot)
2563                .row()
2564                .as_f32();
2565            let scroll_position = editor
2566                .scroll_manager
2567                .anchor()
2568                .scroll_position(&snapshot.display_snapshot);
2569
2570            let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
2571            if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
2572                Some(ScrollPosition {
2573                    cursor,
2574                    offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
2575                })
2576            } else {
2577                None
2578            }
2579        })
2580    }
2581
2582    fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
2583        self.editor.update(cx, |editor, cx| {
2584            let buffer = editor.buffer().read(cx).snapshot(cx);
2585
2586            let excerpt_id = *buffer.as_singleton().unwrap().0;
2587            let mut old_blocks = std::mem::take(&mut self.blocks);
2588            let mut blocks_to_remove: HashMap<_, _> = old_blocks
2589                .iter()
2590                .map(|(message_id, (_, block_id))| (*message_id, *block_id))
2591                .collect();
2592            let mut blocks_to_replace: HashMap<_, RenderBlock> = Default::default();
2593
2594            let render_block = |message: MessageMetadata| -> RenderBlock {
2595                Box::new({
2596                    let context = self.context.clone();
2597                    move |cx| {
2598                        let message_id = MessageId(message.timestamp);
2599                        let llm_loading = message.role == Role::Assistant
2600                            && message.status == MessageStatus::Pending;
2601
2602                        let (label, spinner, note) = match message.role {
2603                            Role::User => (
2604                                Label::new("You").color(Color::Default).into_any_element(),
2605                                None,
2606                                None,
2607                            ),
2608                            Role::Assistant => {
2609                                let base_label = Label::new("Assistant").color(Color::Info);
2610                                let mut spinner = None;
2611                                let mut note = None;
2612                                let animated_label = if llm_loading {
2613                                    base_label
2614                                        .with_animation(
2615                                            "pulsating-label",
2616                                            Animation::new(Duration::from_secs(2))
2617                                                .repeat()
2618                                                .with_easing(pulsating_between(0.3, 0.9)),
2619                                            |label, delta| label.alpha(delta),
2620                                        )
2621                                        .into_any_element()
2622                                } else {
2623                                    base_label.into_any_element()
2624                                };
2625                                if llm_loading {
2626                                    spinner = Some(
2627                                        Icon::new(IconName::ArrowCircle)
2628                                            .size(IconSize::XSmall)
2629                                            .color(Color::Muted)
2630                                            .with_animation(
2631                                                "arrow-circle",
2632                                                Animation::new(Duration::from_secs(2)).repeat(),
2633                                                |icon, delta| {
2634                                                    icon.transform(Transformation::rotate(
2635                                                        percentage(delta),
2636                                                    ))
2637                                                },
2638                                            )
2639                                            .into_any_element(),
2640                                    );
2641                                    note = Some(
2642                                        div()
2643                                            .font(
2644                                                theme::ThemeSettings::get_global(cx)
2645                                                    .buffer_font
2646                                                    .clone(),
2647                                            )
2648                                            .child(
2649                                                Label::new("Press 'esc' to cancel")
2650                                                    .color(Color::Muted)
2651                                                    .size(LabelSize::XSmall),
2652                                            )
2653                                            .into_any_element(),
2654                                    );
2655                                }
2656                                (animated_label, spinner, note)
2657                            }
2658                            Role::System => (
2659                                Label::new("System")
2660                                    .color(Color::Warning)
2661                                    .into_any_element(),
2662                                None,
2663                                None,
2664                            ),
2665                        };
2666
2667                        let sender = h_flex()
2668                            .items_center()
2669                            .gap_2()
2670                            .child(
2671                                ButtonLike::new("role")
2672                                    .style(ButtonStyle::Filled)
2673                                    .child(
2674                                        h_flex()
2675                                            .items_center()
2676                                            .gap_1p5()
2677                                            .child(label)
2678                                            .children(spinner),
2679                                    )
2680                                    .tooltip(|cx| {
2681                                        Tooltip::with_meta(
2682                                            "Toggle message role",
2683                                            None,
2684                                            "Available roles: You (User), Assistant, System",
2685                                            cx,
2686                                        )
2687                                    })
2688                                    .on_click({
2689                                        let context = context.clone();
2690                                        move |_, cx| {
2691                                            context.update(cx, |context, cx| {
2692                                                context.cycle_message_roles(
2693                                                    HashSet::from_iter(Some(message_id)),
2694                                                    cx,
2695                                                )
2696                                            })
2697                                        }
2698                                    }),
2699                            )
2700                            .children(note);
2701
2702                        h_flex()
2703                            .id(("message_header", message_id.as_u64()))
2704                            .pl(cx.gutter_dimensions.full_width())
2705                            .h_11()
2706                            .w_full()
2707                            .relative()
2708                            .gap_1()
2709                            .child(sender)
2710                            .children(match &message.cache {
2711                                Some(cache) if cache.is_final_anchor => match cache.status {
2712                                    CacheStatus::Cached => Some(
2713                                        div()
2714                                            .id("cached")
2715                                            .child(
2716                                                Icon::new(IconName::DatabaseZap)
2717                                                    .size(IconSize::XSmall)
2718                                                    .color(Color::Hint),
2719                                            )
2720                                            .tooltip(|cx| {
2721                                                Tooltip::with_meta(
2722                                                    "Context cached",
2723                                                    None,
2724                                                    "Large messages cached to optimize performance",
2725                                                    cx,
2726                                                )
2727                                            })
2728                                            .into_any_element(),
2729                                    ),
2730                                    CacheStatus::Pending => Some(
2731                                        div()
2732                                            .child(
2733                                                Icon::new(IconName::Ellipsis)
2734                                                    .size(IconSize::XSmall)
2735                                                    .color(Color::Hint),
2736                                            )
2737                                            .into_any_element(),
2738                                    ),
2739                                },
2740                                _ => None,
2741                            })
2742                            .children(match &message.status {
2743                                MessageStatus::Error(error) => Some(
2744                                    Button::new("show-error", "Error")
2745                                        .color(Color::Error)
2746                                        .selected_label_color(Color::Error)
2747                                        .selected_icon_color(Color::Error)
2748                                        .icon(IconName::XCircle)
2749                                        .icon_color(Color::Error)
2750                                        .icon_size(IconSize::Small)
2751                                        .icon_position(IconPosition::Start)
2752                                        .tooltip(move |cx| {
2753                                            Tooltip::with_meta(
2754                                                "Error interacting with language model",
2755                                                None,
2756                                                "Click for more details",
2757                                                cx,
2758                                            )
2759                                        })
2760                                        .on_click({
2761                                            let context = context.clone();
2762                                            let error = error.clone();
2763                                            move |_, cx| {
2764                                                context.update(cx, |_, cx| {
2765                                                    cx.emit(ContextEvent::ShowAssistError(
2766                                                        error.clone(),
2767                                                    ));
2768                                                });
2769                                            }
2770                                        })
2771                                        .into_any_element(),
2772                                ),
2773                                MessageStatus::Canceled => Some(
2774                                    ButtonLike::new("canceled")
2775                                        .child(Icon::new(IconName::XCircle).color(Color::Disabled))
2776                                        .child(
2777                                            Label::new("Canceled")
2778                                                .size(LabelSize::Small)
2779                                                .color(Color::Disabled),
2780                                        )
2781                                        .tooltip(move |cx| {
2782                                            Tooltip::with_meta(
2783                                                "Canceled",
2784                                                None,
2785                                                "Interaction with the assistant was canceled",
2786                                                cx,
2787                                            )
2788                                        })
2789                                        .into_any_element(),
2790                                ),
2791                                _ => None,
2792                            })
2793                            .into_any_element()
2794                    }
2795                })
2796            };
2797            let create_block_properties = |message: &Message| BlockProperties {
2798                height: 2,
2799                style: BlockStyle::Sticky,
2800                placement: BlockPlacement::Above(
2801                    buffer
2802                        .anchor_in_excerpt(excerpt_id, message.anchor_range.start)
2803                        .unwrap(),
2804                ),
2805                priority: usize::MAX,
2806                render: render_block(MessageMetadata::from(message)),
2807            };
2808            let mut new_blocks = vec![];
2809            let mut block_index_to_message = vec![];
2810            for message in self.context.read(cx).messages(cx) {
2811                if let Some(_) = blocks_to_remove.remove(&message.id) {
2812                    // This is an old message that we might modify.
2813                    let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
2814                        debug_assert!(
2815                            false,
2816                            "old_blocks should contain a message_id we've just removed."
2817                        );
2818                        continue;
2819                    };
2820                    // Should we modify it?
2821                    let message_meta = MessageMetadata::from(&message);
2822                    if meta != &message_meta {
2823                        blocks_to_replace.insert(*block_id, render_block(message_meta.clone()));
2824                        *meta = message_meta;
2825                    }
2826                } else {
2827                    // This is a new message.
2828                    new_blocks.push(create_block_properties(&message));
2829                    block_index_to_message.push((message.id, MessageMetadata::from(&message)));
2830                }
2831            }
2832            editor.replace_blocks(blocks_to_replace, None, cx);
2833            editor.remove_blocks(blocks_to_remove.into_values().collect(), None, cx);
2834
2835            let ids = editor.insert_blocks(new_blocks, None, cx);
2836            old_blocks.extend(ids.into_iter().zip(block_index_to_message).map(
2837                |(block_id, (message_id, message_meta))| (message_id, (message_meta, block_id)),
2838            ));
2839            self.blocks = old_blocks;
2840        });
2841    }
2842
2843    /// Returns either the selected text, or the content of the Markdown code
2844    /// block surrounding the cursor.
2845    fn get_selection_or_code_block(
2846        context_editor_view: &View<ContextEditor>,
2847        cx: &mut ViewContext<Workspace>,
2848    ) -> Option<(String, bool)> {
2849        const CODE_FENCE_DELIMITER: &'static str = "```";
2850
2851        let context_editor = context_editor_view.read(cx).editor.clone();
2852        context_editor.update(cx, |context_editor, cx| {
2853            if context_editor.selections.newest::<Point>(cx).is_empty() {
2854                let snapshot = context_editor.buffer().read(cx).snapshot(cx);
2855                let (_, _, snapshot) = snapshot.as_singleton()?;
2856
2857                let head = context_editor.selections.newest::<Point>(cx).head();
2858                let offset = snapshot.point_to_offset(head);
2859
2860                let surrounding_code_block_range = find_surrounding_code_block(snapshot, offset)?;
2861                let mut text = snapshot
2862                    .text_for_range(surrounding_code_block_range)
2863                    .collect::<String>();
2864
2865                // If there is no newline trailing the closing three-backticks, then
2866                // tree-sitter-md extends the range of the content node to include
2867                // the backticks.
2868                if text.ends_with(CODE_FENCE_DELIMITER) {
2869                    text.drain((text.len() - CODE_FENCE_DELIMITER.len())..);
2870                }
2871
2872                (!text.is_empty()).then_some((text, true))
2873            } else {
2874                let anchor = context_editor.selections.newest_anchor();
2875                let text = context_editor
2876                    .buffer()
2877                    .read(cx)
2878                    .read(cx)
2879                    .text_for_range(anchor.range())
2880                    .collect::<String>();
2881
2882                (!text.is_empty()).then_some((text, false))
2883            }
2884        })
2885    }
2886
2887    fn insert_selection(
2888        workspace: &mut Workspace,
2889        _: &InsertIntoEditor,
2890        cx: &mut ViewContext<Workspace>,
2891    ) {
2892        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2893            return;
2894        };
2895        let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
2896            return;
2897        };
2898        let Some(active_editor_view) = workspace
2899            .active_item(cx)
2900            .and_then(|item| item.act_as::<Editor>(cx))
2901        else {
2902            return;
2903        };
2904
2905        if let Some((text, _)) = Self::get_selection_or_code_block(&context_editor_view, cx) {
2906            active_editor_view.update(cx, |editor, cx| {
2907                editor.insert(&text, cx);
2908                editor.focus(cx);
2909            })
2910        }
2911    }
2912
2913    fn copy_code(workspace: &mut Workspace, _: &CopyCode, cx: &mut ViewContext<Workspace>) {
2914        let result = maybe!({
2915            let panel = workspace.panel::<AssistantPanel>(cx)?;
2916            let context_editor_view = panel.read(cx).active_context_editor(cx)?;
2917            Self::get_selection_or_code_block(&context_editor_view, cx)
2918        });
2919        let Some((text, is_code_block)) = result else {
2920            return;
2921        };
2922
2923        cx.write_to_clipboard(ClipboardItem::new_string(text));
2924
2925        struct CopyToClipboardToast;
2926        workspace.show_toast(
2927            Toast::new(
2928                NotificationId::unique::<CopyToClipboardToast>(),
2929                format!(
2930                    "{} copied to clipboard.",
2931                    if is_code_block {
2932                        "Code block"
2933                    } else {
2934                        "Selection"
2935                    }
2936                ),
2937            )
2938            .autohide(),
2939            cx,
2940        );
2941    }
2942
2943    fn insert_dragged_files(
2944        workspace: &mut Workspace,
2945        action: &InsertDraggedFiles,
2946        cx: &mut ViewContext<Workspace>,
2947    ) {
2948        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2949            return;
2950        };
2951        let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
2952            return;
2953        };
2954
2955        let project = workspace.project().clone();
2956
2957        let paths = match action {
2958            InsertDraggedFiles::ProjectPaths(paths) => Task::ready((paths.clone(), vec![])),
2959            InsertDraggedFiles::ExternalFiles(paths) => {
2960                let tasks = paths
2961                    .clone()
2962                    .into_iter()
2963                    .map(|path| Workspace::project_path_for_path(project.clone(), &path, false, cx))
2964                    .collect::<Vec<_>>();
2965
2966                cx.spawn(move |_, cx| async move {
2967                    let mut paths = vec![];
2968                    let mut worktrees = vec![];
2969
2970                    let opened_paths = futures::future::join_all(tasks).await;
2971                    for (worktree, project_path) in opened_paths.into_iter().flatten() {
2972                        let Ok(worktree_root_name) =
2973                            worktree.read_with(&cx, |worktree, _| worktree.root_name().to_string())
2974                        else {
2975                            continue;
2976                        };
2977
2978                        let mut full_path = PathBuf::from(worktree_root_name.clone());
2979                        full_path.push(&project_path.path);
2980                        paths.push(full_path);
2981                        worktrees.push(worktree);
2982                    }
2983
2984                    (paths, worktrees)
2985                })
2986            }
2987        };
2988
2989        cx.spawn(|_, mut cx| async move {
2990            let (paths, dragged_file_worktrees) = paths.await;
2991            let cmd_name = file_command::FileSlashCommand.name();
2992
2993            context_editor_view
2994                .update(&mut cx, |context_editor, cx| {
2995                    let file_argument = paths
2996                        .into_iter()
2997                        .map(|path| path.to_string_lossy().to_string())
2998                        .collect::<Vec<_>>()
2999                        .join(" ");
3000
3001                    context_editor.editor.update(cx, |editor, cx| {
3002                        editor.insert("\n", cx);
3003                        editor.insert(&format!("/{} {}", cmd_name, file_argument), cx);
3004                    });
3005
3006                    context_editor.confirm_command(&ConfirmCommand, cx);
3007
3008                    context_editor
3009                        .dragged_file_worktrees
3010                        .extend(dragged_file_worktrees);
3011                })
3012                .log_err();
3013        })
3014        .detach();
3015    }
3016
3017    fn quote_selection(
3018        workspace: &mut Workspace,
3019        _: &QuoteSelection,
3020        cx: &mut ViewContext<Workspace>,
3021    ) {
3022        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3023            return;
3024        };
3025
3026        let Some(creases) = selections_creases(workspace, cx) else {
3027            return;
3028        };
3029
3030        if creases.is_empty() {
3031            return;
3032        }
3033        // Activate the panel
3034        if !panel.focus_handle(cx).contains_focused(cx) {
3035            workspace.toggle_panel_focus::<AssistantPanel>(cx);
3036        }
3037
3038        panel.update(cx, |_, cx| {
3039            // Wait to create a new context until the workspace is no longer
3040            // being updated.
3041            cx.defer(move |panel, cx| {
3042                if let Some(context) = panel
3043                    .active_context_editor(cx)
3044                    .or_else(|| panel.new_context(cx))
3045                {
3046                    context.update(cx, |context, cx| {
3047                        context.editor.update(cx, |editor, cx| {
3048                            editor.insert("\n", cx);
3049                            for (text, crease_title) in creases {
3050                                let point = editor.selections.newest::<Point>(cx).head();
3051                                let start_row = MultiBufferRow(point.row);
3052
3053                                editor.insert(&text, cx);
3054
3055                                let snapshot = editor.buffer().read(cx).snapshot(cx);
3056                                let anchor_before = snapshot.anchor_after(point);
3057                                let anchor_after = editor
3058                                    .selections
3059                                    .newest_anchor()
3060                                    .head()
3061                                    .bias_left(&snapshot);
3062
3063                                editor.insert("\n", cx);
3064
3065                                let fold_placeholder = quote_selection_fold_placeholder(
3066                                    crease_title,
3067                                    cx.view().downgrade(),
3068                                );
3069                                let crease = Crease::new(
3070                                    anchor_before..anchor_after,
3071                                    fold_placeholder,
3072                                    render_quote_selection_output_toggle,
3073                                    |_, _, _| Empty.into_any(),
3074                                );
3075                                editor.insert_creases(vec![crease], cx);
3076                                editor.fold_at(
3077                                    &FoldAt {
3078                                        buffer_row: start_row,
3079                                    },
3080                                    cx,
3081                                );
3082                            }
3083                        })
3084                    });
3085                };
3086            });
3087        });
3088    }
3089
3090    fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
3091        if self.editor.read(cx).selections.count() == 1 {
3092            let (copied_text, metadata, _) = self.get_clipboard_contents(cx);
3093            cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
3094                copied_text,
3095                metadata,
3096            ));
3097            cx.stop_propagation();
3098            return;
3099        }
3100
3101        cx.propagate();
3102    }
3103
3104    fn cut(&mut self, _: &editor::actions::Cut, cx: &mut ViewContext<Self>) {
3105        if self.editor.read(cx).selections.count() == 1 {
3106            let (copied_text, metadata, selections) = self.get_clipboard_contents(cx);
3107
3108            self.editor.update(cx, |editor, cx| {
3109                editor.transact(cx, |this, cx| {
3110                    this.change_selections(Some(Autoscroll::fit()), cx, |s| {
3111                        s.select(selections);
3112                    });
3113                    this.insert("", cx);
3114                    cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
3115                        copied_text,
3116                        metadata,
3117                    ));
3118                });
3119            });
3120
3121            cx.stop_propagation();
3122            return;
3123        }
3124
3125        cx.propagate();
3126    }
3127
3128    fn get_clipboard_contents(
3129        &mut self,
3130        cx: &mut ViewContext<Self>,
3131    ) -> (String, CopyMetadata, Vec<text::Selection<usize>>) {
3132        let (snapshot, selection, creases) = self.editor.update(cx, |editor, cx| {
3133            let mut selection = editor.selections.newest::<Point>(cx);
3134            let snapshot = editor.buffer().read(cx).snapshot(cx);
3135
3136            let is_entire_line = selection.is_empty() || editor.selections.line_mode;
3137            if is_entire_line {
3138                selection.start = Point::new(selection.start.row, 0);
3139                selection.end =
3140                    cmp::min(snapshot.max_point(), Point::new(selection.start.row + 1, 0));
3141                selection.goal = SelectionGoal::None;
3142            }
3143
3144            let selection_start = snapshot.point_to_offset(selection.start);
3145
3146            (
3147                snapshot.clone(),
3148                selection.clone(),
3149                editor.display_map.update(cx, |display_map, cx| {
3150                    display_map
3151                        .snapshot(cx)
3152                        .crease_snapshot
3153                        .creases_in_range(
3154                            MultiBufferRow(selection.start.row)
3155                                ..MultiBufferRow(selection.end.row + 1),
3156                            &snapshot,
3157                        )
3158                        .filter_map(|crease| {
3159                            if let Some(metadata) = &crease.metadata {
3160                                let start = crease
3161                                    .range
3162                                    .start
3163                                    .to_offset(&snapshot)
3164                                    .saturating_sub(selection_start);
3165                                let end = crease
3166                                    .range
3167                                    .end
3168                                    .to_offset(&snapshot)
3169                                    .saturating_sub(selection_start);
3170
3171                                let range_relative_to_selection = start..end;
3172
3173                                if range_relative_to_selection.is_empty() {
3174                                    None
3175                                } else {
3176                                    Some(SelectedCreaseMetadata {
3177                                        range_relative_to_selection,
3178                                        crease: metadata.clone(),
3179                                    })
3180                                }
3181                            } else {
3182                                None
3183                            }
3184                        })
3185                        .collect::<Vec<_>>()
3186                }),
3187            )
3188        });
3189
3190        let selection = selection.map(|point| snapshot.point_to_offset(point));
3191        let context = self.context.read(cx);
3192
3193        let mut text = String::new();
3194        for message in context.messages(cx) {
3195            if message.offset_range.start >= selection.range().end {
3196                break;
3197            } else if message.offset_range.end >= selection.range().start {
3198                let range = cmp::max(message.offset_range.start, selection.range().start)
3199                    ..cmp::min(message.offset_range.end, selection.range().end);
3200                if !range.is_empty() {
3201                    for chunk in context.buffer().read(cx).text_for_range(range) {
3202                        text.push_str(chunk);
3203                    }
3204                    if message.offset_range.end < selection.range().end {
3205                        text.push('\n');
3206                    }
3207                }
3208            }
3209        }
3210
3211        (text, CopyMetadata { creases }, vec![selection])
3212    }
3213
3214    fn paste(&mut self, action: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
3215        cx.stop_propagation();
3216
3217        let images = if let Some(item) = cx.read_from_clipboard() {
3218            item.into_entries()
3219                .filter_map(|entry| {
3220                    if let ClipboardEntry::Image(image) = entry {
3221                        Some(image)
3222                    } else {
3223                        None
3224                    }
3225                })
3226                .collect()
3227        } else {
3228            Vec::new()
3229        };
3230
3231        let metadata = if let Some(item) = cx.read_from_clipboard() {
3232            item.entries().first().and_then(|entry| {
3233                if let ClipboardEntry::String(text) = entry {
3234                    text.metadata_json::<CopyMetadata>()
3235                } else {
3236                    None
3237                }
3238            })
3239        } else {
3240            None
3241        };
3242
3243        if images.is_empty() {
3244            self.editor.update(cx, |editor, cx| {
3245                let paste_position = editor.selections.newest::<usize>(cx).head();
3246                editor.paste(action, cx);
3247
3248                if let Some(metadata) = metadata {
3249                    let buffer = editor.buffer().read(cx).snapshot(cx);
3250
3251                    let mut buffer_rows_to_fold = BTreeSet::new();
3252                    let weak_editor = cx.view().downgrade();
3253                    editor.insert_creases(
3254                        metadata.creases.into_iter().map(|metadata| {
3255                            let start = buffer.anchor_after(
3256                                paste_position + metadata.range_relative_to_selection.start,
3257                            );
3258                            let end = buffer.anchor_before(
3259                                paste_position + metadata.range_relative_to_selection.end,
3260                            );
3261
3262                            let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
3263                            buffer_rows_to_fold.insert(buffer_row);
3264                            Crease::new(
3265                                start..end,
3266                                FoldPlaceholder {
3267                                    constrain_width: false,
3268                                    render: render_fold_icon_button(
3269                                        weak_editor.clone(),
3270                                        metadata.crease.icon,
3271                                        metadata.crease.label.clone(),
3272                                    ),
3273                                    merge_adjacent: false,
3274                                },
3275                                render_slash_command_output_toggle,
3276                                |_, _, _| Empty.into_any(),
3277                            )
3278                            .with_metadata(metadata.crease.clone())
3279                        }),
3280                        cx,
3281                    );
3282                    for buffer_row in buffer_rows_to_fold.into_iter().rev() {
3283                        editor.fold_at(&FoldAt { buffer_row }, cx);
3284                    }
3285                }
3286            });
3287        } else {
3288            let mut image_positions = Vec::new();
3289            self.editor.update(cx, |editor, cx| {
3290                editor.transact(cx, |editor, cx| {
3291                    let edits = editor
3292                        .selections
3293                        .all::<usize>(cx)
3294                        .into_iter()
3295                        .map(|selection| (selection.start..selection.end, "\n"));
3296                    editor.edit(edits, cx);
3297
3298                    let snapshot = editor.buffer().read(cx).snapshot(cx);
3299                    for selection in editor.selections.all::<usize>(cx) {
3300                        image_positions.push(snapshot.anchor_before(selection.end));
3301                    }
3302                });
3303            });
3304
3305            self.context.update(cx, |context, cx| {
3306                for image in images {
3307                    let Some(render_image) = image.to_image_data(cx).log_err() else {
3308                        continue;
3309                    };
3310                    let image_id = image.id();
3311                    let image_task = LanguageModelImage::from_image(image, cx).shared();
3312
3313                    for image_position in image_positions.iter() {
3314                        context.insert_content(
3315                            Content::Image {
3316                                anchor: image_position.text_anchor,
3317                                image_id,
3318                                image: image_task.clone(),
3319                                render_image: render_image.clone(),
3320                            },
3321                            cx,
3322                        );
3323                    }
3324                }
3325            });
3326        }
3327    }
3328
3329    fn update_image_blocks(&mut self, cx: &mut ViewContext<Self>) {
3330        self.editor.update(cx, |editor, cx| {
3331            let buffer = editor.buffer().read(cx).snapshot(cx);
3332            let excerpt_id = *buffer.as_singleton().unwrap().0;
3333            let old_blocks = std::mem::take(&mut self.image_blocks);
3334            let new_blocks = self
3335                .context
3336                .read(cx)
3337                .contents(cx)
3338                .filter_map(|content| {
3339                    if let Content::Image {
3340                        anchor,
3341                        render_image,
3342                        ..
3343                    } = content
3344                    {
3345                        Some((anchor, render_image))
3346                    } else {
3347                        None
3348                    }
3349                })
3350                .filter_map(|(anchor, render_image)| {
3351                    const MAX_HEIGHT_IN_LINES: u32 = 8;
3352                    let anchor = buffer.anchor_in_excerpt(excerpt_id, anchor).unwrap();
3353                    let image = render_image.clone();
3354                    anchor.is_valid(&buffer).then(|| BlockProperties {
3355                        placement: BlockPlacement::Above(anchor),
3356                        height: MAX_HEIGHT_IN_LINES,
3357                        style: BlockStyle::Sticky,
3358                        render: Box::new(move |cx| {
3359                            let image_size = size_for_image(
3360                                &image,
3361                                size(
3362                                    cx.max_width - cx.gutter_dimensions.full_width(),
3363                                    MAX_HEIGHT_IN_LINES as f32 * cx.line_height,
3364                                ),
3365                            );
3366                            h_flex()
3367                                .pl(cx.gutter_dimensions.full_width())
3368                                .child(
3369                                    img(image.clone())
3370                                        .object_fit(gpui::ObjectFit::ScaleDown)
3371                                        .w(image_size.width)
3372                                        .h(image_size.height),
3373                                )
3374                                .into_any_element()
3375                        }),
3376                        priority: 0,
3377                    })
3378                })
3379                .collect::<Vec<_>>();
3380
3381            editor.remove_blocks(old_blocks, None, cx);
3382            let ids = editor.insert_blocks(new_blocks, None, cx);
3383            self.image_blocks = HashSet::from_iter(ids);
3384        });
3385    }
3386
3387    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
3388        self.context.update(cx, |context, cx| {
3389            let selections = self.editor.read(cx).selections.disjoint_anchors();
3390            for selection in selections.as_ref() {
3391                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
3392                let range = selection
3393                    .map(|endpoint| endpoint.to_offset(&buffer))
3394                    .range();
3395                context.split_message(range, cx);
3396            }
3397        });
3398    }
3399
3400    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
3401        self.context.update(cx, |context, cx| {
3402            context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
3403        });
3404    }
3405
3406    fn title(&self, cx: &AppContext) -> Cow<str> {
3407        self.context
3408            .read(cx)
3409            .summary()
3410            .map(|summary| summary.text.clone())
3411            .map(Cow::Owned)
3412            .unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
3413    }
3414
3415    fn render_patch_header(
3416        &self,
3417        range: Range<text::Anchor>,
3418        _id: FoldId,
3419        cx: &mut ViewContext<Self>,
3420    ) -> Option<AnyElement> {
3421        let patch = self.context.read(cx).patch_for_range(&range, cx)?;
3422        let theme = cx.theme().clone();
3423        Some(
3424            h_flex()
3425                .px_1()
3426                .py_0p5()
3427                .border_b_1()
3428                .border_color(theme.status().info_border)
3429                .gap_1()
3430                .child(Icon::new(IconName::Diff).size(IconSize::Small))
3431                .child(Label::new(patch.title.clone()).size(LabelSize::Small))
3432                .into_any(),
3433        )
3434    }
3435
3436    fn render_patch_footer(
3437        &mut self,
3438        range: Range<text::Anchor>,
3439        max_width: Pixels,
3440        gutter_width: Pixels,
3441        id: BlockId,
3442        cx: &mut ViewContext<Self>,
3443    ) -> Option<AnyElement> {
3444        let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
3445        let (excerpt_id, _buffer_id, _) = snapshot.buffer_snapshot.as_singleton().unwrap();
3446        let excerpt_id = *excerpt_id;
3447        let anchor = snapshot
3448            .buffer_snapshot
3449            .anchor_in_excerpt(excerpt_id, range.start)
3450            .unwrap();
3451
3452        if !snapshot.intersects_fold(anchor) {
3453            return None;
3454        }
3455
3456        let patch = self.context.read(cx).patch_for_range(&range, cx)?;
3457        let paths = patch
3458            .paths()
3459            .map(|p| SharedString::from(p.to_string()))
3460            .collect::<BTreeSet<_>>();
3461
3462        Some(
3463            v_flex()
3464                .id(id)
3465                .pl(gutter_width)
3466                .w(max_width)
3467                .py_2()
3468                .cursor(CursorStyle::PointingHand)
3469                .on_click(cx.listener(move |this, _, cx| {
3470                    this.editor.update(cx, |editor, cx| {
3471                        editor.change_selections(None, cx, |selections| {
3472                            selections.select_ranges(vec![anchor..anchor]);
3473                        });
3474                    });
3475                    this.focus_active_patch(cx);
3476                }))
3477                .children(paths.into_iter().map(|path| {
3478                    h_flex()
3479                        .pl_1()
3480                        .gap_1()
3481                        .child(Icon::new(IconName::File).size(IconSize::Small))
3482                        .child(Label::new(path).size(LabelSize::Small))
3483                }))
3484                .when(patch.status == AssistantPatchStatus::Pending, |div| {
3485                    div.child(
3486                        Label::new("Generating")
3487                            .color(Color::Muted)
3488                            .size(LabelSize::Small)
3489                            .with_animation(
3490                                "pulsating-label",
3491                                Animation::new(Duration::from_secs(2))
3492                                    .repeat()
3493                                    .with_easing(pulsating_between(0.4, 1.)),
3494                                |label, delta| label.alpha(delta),
3495                            ),
3496                    )
3497                })
3498                .into_any(),
3499        )
3500    }
3501
3502    fn render_notice(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
3503        use feature_flags::FeatureFlagAppExt;
3504        let nudge = self.assistant_panel.upgrade().map(|assistant_panel| {
3505            assistant_panel.read(cx).show_zed_ai_notice && cx.has_flag::<feature_flags::ZedPro>()
3506        });
3507
3508        if nudge.map_or(false, |value| value) {
3509            Some(
3510                h_flex()
3511                    .p_3()
3512                    .border_b_1()
3513                    .border_color(cx.theme().colors().border_variant)
3514                    .bg(cx.theme().colors().editor_background)
3515                    .justify_between()
3516                    .child(
3517                        h_flex()
3518                            .gap_3()
3519                            .child(Icon::new(IconName::ZedAssistant).color(Color::Accent))
3520                            .child(Label::new("Zed AI is here! Get started by signing in →")),
3521                    )
3522                    .child(
3523                        Button::new("sign-in", "Sign in")
3524                            .size(ButtonSize::Compact)
3525                            .style(ButtonStyle::Filled)
3526                            .on_click(cx.listener(|this, _event, cx| {
3527                                let client = this
3528                                    .workspace
3529                                    .update(cx, |workspace, _| workspace.client().clone())
3530                                    .log_err();
3531
3532                                if let Some(client) = client {
3533                                    cx.spawn(|this, mut cx| async move {
3534                                        client.authenticate_and_connect(true, &mut cx).await?;
3535                                        this.update(&mut cx, |_, cx| cx.notify())
3536                                    })
3537                                    .detach_and_log_err(cx)
3538                                }
3539                            })),
3540                    )
3541                    .into_any_element(),
3542            )
3543        } else if let Some(configuration_error) = configuration_error(cx) {
3544            let label = match configuration_error {
3545                ConfigurationError::NoProvider => "No LLM provider selected.",
3546                ConfigurationError::ProviderNotAuthenticated => "LLM provider is not configured.",
3547            };
3548            Some(
3549                h_flex()
3550                    .px_3()
3551                    .py_2()
3552                    .border_b_1()
3553                    .border_color(cx.theme().colors().border_variant)
3554                    .bg(cx.theme().colors().editor_background)
3555                    .justify_between()
3556                    .child(
3557                        h_flex()
3558                            .gap_3()
3559                            .child(
3560                                Icon::new(IconName::Warning)
3561                                    .size(IconSize::Small)
3562                                    .color(Color::Warning),
3563                            )
3564                            .child(Label::new(label)),
3565                    )
3566                    .child(
3567                        Button::new("open-configuration", "Configure Providers")
3568                            .size(ButtonSize::Compact)
3569                            .icon(Some(IconName::SlidersVertical))
3570                            .icon_size(IconSize::Small)
3571                            .icon_position(IconPosition::Start)
3572                            .style(ButtonStyle::Filled)
3573                            .on_click({
3574                                let focus_handle = self.focus_handle(cx).clone();
3575                                move |_event, cx| {
3576                                    focus_handle.dispatch_action(&ShowConfiguration, cx);
3577                                }
3578                            }),
3579                    )
3580                    .into_any_element(),
3581            )
3582        } else {
3583            None
3584        }
3585    }
3586
3587    fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3588        let focus_handle = self.focus_handle(cx).clone();
3589
3590        let (style, tooltip) = match token_state(&self.context, cx) {
3591            Some(TokenState::NoTokensLeft { .. }) => (
3592                ButtonStyle::Tinted(TintColor::Negative),
3593                Some(Tooltip::text("Token limit reached", cx)),
3594            ),
3595            Some(TokenState::HasMoreTokens {
3596                over_warn_threshold,
3597                ..
3598            }) => {
3599                let (style, tooltip) = if over_warn_threshold {
3600                    (
3601                        ButtonStyle::Tinted(TintColor::Warning),
3602                        Some(Tooltip::text("Token limit is close to exhaustion", cx)),
3603                    )
3604                } else {
3605                    (ButtonStyle::Filled, None)
3606                };
3607                (style, tooltip)
3608            }
3609            None => (ButtonStyle::Filled, None),
3610        };
3611
3612        let provider = LanguageModelRegistry::read_global(cx).active_provider();
3613
3614        let has_configuration_error = configuration_error(cx).is_some();
3615        let needs_to_accept_terms = self.show_accept_terms
3616            && provider
3617                .as_ref()
3618                .map_or(false, |provider| provider.must_accept_terms(cx));
3619        let disabled = has_configuration_error || needs_to_accept_terms;
3620
3621        ButtonLike::new("send_button")
3622            .disabled(disabled)
3623            .style(style)
3624            .when_some(tooltip, |button, tooltip| {
3625                button.tooltip(move |_| tooltip.clone())
3626            })
3627            .layer(ElevationIndex::ModalSurface)
3628            .child(Label::new(
3629                if AssistantSettings::get_global(cx).are_live_diffs_enabled(cx) {
3630                    "Chat"
3631                } else {
3632                    "Send"
3633                },
3634            ))
3635            .children(
3636                KeyBinding::for_action_in(&Assist, &focus_handle, cx)
3637                    .map(|binding| binding.into_any_element()),
3638            )
3639            .on_click(move |_event, cx| {
3640                focus_handle.dispatch_action(&Assist, cx);
3641            })
3642    }
3643
3644    fn render_edit_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3645        let focus_handle = self.focus_handle(cx).clone();
3646
3647        let (style, tooltip) = match token_state(&self.context, cx) {
3648            Some(TokenState::NoTokensLeft { .. }) => (
3649                ButtonStyle::Tinted(TintColor::Negative),
3650                Some(Tooltip::text("Token limit reached", cx)),
3651            ),
3652            Some(TokenState::HasMoreTokens {
3653                over_warn_threshold,
3654                ..
3655            }) => {
3656                let (style, tooltip) = if over_warn_threshold {
3657                    (
3658                        ButtonStyle::Tinted(TintColor::Warning),
3659                        Some(Tooltip::text("Token limit is close to exhaustion", cx)),
3660                    )
3661                } else {
3662                    (ButtonStyle::Filled, None)
3663                };
3664                (style, tooltip)
3665            }
3666            None => (ButtonStyle::Filled, None),
3667        };
3668
3669        let provider = LanguageModelRegistry::read_global(cx).active_provider();
3670
3671        let has_configuration_error = configuration_error(cx).is_some();
3672        let needs_to_accept_terms = self.show_accept_terms
3673            && provider
3674                .as_ref()
3675                .map_or(false, |provider| provider.must_accept_terms(cx));
3676        let disabled = has_configuration_error || needs_to_accept_terms;
3677
3678        ButtonLike::new("edit_button")
3679            .disabled(disabled)
3680            .style(style)
3681            .when_some(tooltip, |button, tooltip| {
3682                button.tooltip(move |_| tooltip.clone())
3683            })
3684            .layer(ElevationIndex::ModalSurface)
3685            .child(Label::new("Suggest Edits"))
3686            .children(
3687                KeyBinding::for_action_in(&Edit, &focus_handle, cx)
3688                    .map(|binding| binding.into_any_element()),
3689            )
3690            .on_click(move |_event, cx| {
3691                focus_handle.dispatch_action(&Edit, cx);
3692            })
3693    }
3694
3695    fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
3696        let last_error = self.last_error.as_ref()?;
3697
3698        Some(
3699            div()
3700                .absolute()
3701                .right_3()
3702                .bottom_12()
3703                .max_w_96()
3704                .py_2()
3705                .px_3()
3706                .elevation_2(cx)
3707                .occlude()
3708                .child(match last_error {
3709                    AssistError::FileRequired => self.render_file_required_error(cx),
3710                    AssistError::PaymentRequired => self.render_payment_required_error(cx),
3711                    AssistError::MaxMonthlySpendReached => {
3712                        self.render_max_monthly_spend_reached_error(cx)
3713                    }
3714                    AssistError::Message(error_message) => {
3715                        self.render_assist_error(error_message, cx)
3716                    }
3717                })
3718                .into_any(),
3719        )
3720    }
3721
3722    fn render_file_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
3723        v_flex()
3724            .gap_0p5()
3725            .child(
3726                h_flex()
3727                    .gap_1p5()
3728                    .items_center()
3729                    .child(Icon::new(IconName::Warning).color(Color::Warning))
3730                    .child(
3731                        Label::new("Suggest Edits needs a file to edit").weight(FontWeight::MEDIUM),
3732                    ),
3733            )
3734            .child(
3735                div()
3736                    .id("error-message")
3737                    .max_h_24()
3738                    .overflow_y_scroll()
3739                    .child(Label::new(
3740                        "To include files, type /file or /tab in your prompt.",
3741                    )),
3742            )
3743            .child(
3744                h_flex()
3745                    .justify_end()
3746                    .mt_1()
3747                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
3748                        |this, _, cx| {
3749                            this.last_error = None;
3750                            cx.notify();
3751                        },
3752                    ))),
3753            )
3754            .into_any()
3755    }
3756
3757    fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
3758        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.";
3759
3760        v_flex()
3761            .gap_0p5()
3762            .child(
3763                h_flex()
3764                    .gap_1p5()
3765                    .items_center()
3766                    .child(Icon::new(IconName::XCircle).color(Color::Error))
3767                    .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
3768            )
3769            .child(
3770                div()
3771                    .id("error-message")
3772                    .max_h_24()
3773                    .overflow_y_scroll()
3774                    .child(Label::new(ERROR_MESSAGE)),
3775            )
3776            .child(
3777                h_flex()
3778                    .justify_end()
3779                    .mt_1()
3780                    .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
3781                        |this, _, cx| {
3782                            this.last_error = None;
3783                            cx.open_url(&zed_urls::account_url(cx));
3784                            cx.notify();
3785                        },
3786                    )))
3787                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
3788                        |this, _, cx| {
3789                            this.last_error = None;
3790                            cx.notify();
3791                        },
3792                    ))),
3793            )
3794            .into_any()
3795    }
3796
3797    fn render_max_monthly_spend_reached_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
3798        const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
3799
3800        v_flex()
3801            .gap_0p5()
3802            .child(
3803                h_flex()
3804                    .gap_1p5()
3805                    .items_center()
3806                    .child(Icon::new(IconName::XCircle).color(Color::Error))
3807                    .child(Label::new("Max Monthly Spend Reached").weight(FontWeight::MEDIUM)),
3808            )
3809            .child(
3810                div()
3811                    .id("error-message")
3812                    .max_h_24()
3813                    .overflow_y_scroll()
3814                    .child(Label::new(ERROR_MESSAGE)),
3815            )
3816            .child(
3817                h_flex()
3818                    .justify_end()
3819                    .mt_1()
3820                    .child(
3821                        Button::new("subscribe", "Update Monthly Spend Limit").on_click(
3822                            cx.listener(|this, _, cx| {
3823                                this.last_error = None;
3824                                cx.open_url(&zed_urls::account_url(cx));
3825                                cx.notify();
3826                            }),
3827                        ),
3828                    )
3829                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
3830                        |this, _, cx| {
3831                            this.last_error = None;
3832                            cx.notify();
3833                        },
3834                    ))),
3835            )
3836            .into_any()
3837    }
3838
3839    fn render_assist_error(
3840        &self,
3841        error_message: &SharedString,
3842        cx: &mut ViewContext<Self>,
3843    ) -> AnyElement {
3844        v_flex()
3845            .gap_0p5()
3846            .child(
3847                h_flex()
3848                    .gap_1p5()
3849                    .items_center()
3850                    .child(Icon::new(IconName::XCircle).color(Color::Error))
3851                    .child(
3852                        Label::new("Error interacting with language model")
3853                            .weight(FontWeight::MEDIUM),
3854                    ),
3855            )
3856            .child(
3857                div()
3858                    .id("error-message")
3859                    .max_h_24()
3860                    .overflow_y_scroll()
3861                    .child(Label::new(error_message.clone())),
3862            )
3863            .child(
3864                h_flex()
3865                    .justify_end()
3866                    .mt_1()
3867                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
3868                        |this, _, cx| {
3869                            this.last_error = None;
3870                            cx.notify();
3871                        },
3872                    ))),
3873            )
3874            .into_any()
3875    }
3876}
3877
3878/// Returns the contents of the *outermost* fenced code block that contains the given offset.
3879fn find_surrounding_code_block(snapshot: &BufferSnapshot, offset: usize) -> Option<Range<usize>> {
3880    const CODE_BLOCK_NODE: &'static str = "fenced_code_block";
3881    const CODE_BLOCK_CONTENT: &'static str = "code_fence_content";
3882
3883    let layer = snapshot.syntax_layers().next()?;
3884
3885    let root_node = layer.node();
3886    let mut cursor = root_node.walk();
3887
3888    // Go to the first child for the given offset
3889    while cursor.goto_first_child_for_byte(offset).is_some() {
3890        // If we're at the end of the node, go to the next one.
3891        // Example: if you have a fenced-code-block, and you're on the start of the line
3892        // right after the closing ```, you want to skip the fenced-code-block and
3893        // go to the next sibling.
3894        if cursor.node().end_byte() == offset {
3895            cursor.goto_next_sibling();
3896        }
3897
3898        if cursor.node().start_byte() > offset {
3899            break;
3900        }
3901
3902        // We found the fenced code block.
3903        if cursor.node().kind() == CODE_BLOCK_NODE {
3904            // Now we need to find the child node that contains the code.
3905            cursor.goto_first_child();
3906            loop {
3907                if cursor.node().kind() == CODE_BLOCK_CONTENT {
3908                    return Some(cursor.node().byte_range());
3909                }
3910                if !cursor.goto_next_sibling() {
3911                    break;
3912                }
3913            }
3914        }
3915    }
3916
3917    None
3918}
3919
3920pub fn selections_creases(
3921    workspace: &mut workspace::Workspace,
3922    cx: &mut ViewContext<Workspace>,
3923) -> Option<Vec<(String, String)>> {
3924    let editor = workspace
3925        .active_item(cx)
3926        .and_then(|item| item.act_as::<Editor>(cx))?;
3927
3928    let mut creases = vec![];
3929    editor.update(cx, |editor, cx| {
3930        let selections = editor.selections.all_adjusted(cx);
3931        let buffer = editor.buffer().read(cx).snapshot(cx);
3932        for selection in selections {
3933            let range = editor::ToOffset::to_offset(&selection.start, &buffer)
3934                ..editor::ToOffset::to_offset(&selection.end, &buffer);
3935            let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
3936            if selected_text.is_empty() {
3937                continue;
3938            }
3939            let start_language = buffer.language_at(range.start);
3940            let end_language = buffer.language_at(range.end);
3941            let language_name = if start_language == end_language {
3942                start_language.map(|language| language.code_fence_block_name())
3943            } else {
3944                None
3945            };
3946            let language_name = language_name.as_deref().unwrap_or("");
3947            let filename = buffer
3948                .file_at(selection.start)
3949                .map(|file| file.full_path(cx));
3950            let text = if language_name == "markdown" {
3951                selected_text
3952                    .lines()
3953                    .map(|line| format!("> {}", line))
3954                    .collect::<Vec<_>>()
3955                    .join("\n")
3956            } else {
3957                let start_symbols = buffer
3958                    .symbols_containing(selection.start, None)
3959                    .map(|(_, symbols)| symbols);
3960                let end_symbols = buffer
3961                    .symbols_containing(selection.end, None)
3962                    .map(|(_, symbols)| symbols);
3963
3964                let outline_text =
3965                    if let Some((start_symbols, end_symbols)) = start_symbols.zip(end_symbols) {
3966                        Some(
3967                            start_symbols
3968                                .into_iter()
3969                                .zip(end_symbols)
3970                                .take_while(|(a, b)| a == b)
3971                                .map(|(a, _)| a.text)
3972                                .collect::<Vec<_>>()
3973                                .join(" > "),
3974                        )
3975                    } else {
3976                        None
3977                    };
3978
3979                let line_comment_prefix = start_language
3980                    .and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
3981
3982                let fence = codeblock_fence_for_path(
3983                    filename.as_deref(),
3984                    Some(selection.start.row..=selection.end.row),
3985                );
3986
3987                if let Some((line_comment_prefix, outline_text)) =
3988                    line_comment_prefix.zip(outline_text)
3989                {
3990                    let breadcrumb = format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
3991                    format!("{fence}{breadcrumb}{selected_text}\n```")
3992                } else {
3993                    format!("{fence}{selected_text}\n```")
3994                }
3995            };
3996            let crease_title = if let Some(path) = filename {
3997                let start_line = selection.start.row + 1;
3998                let end_line = selection.end.row + 1;
3999                if start_line == end_line {
4000                    format!("{}, Line {}", path.display(), start_line)
4001                } else {
4002                    format!("{}, Lines {} to {}", path.display(), start_line, end_line)
4003                }
4004            } else {
4005                "Quoted selection".to_string()
4006            };
4007            creases.push((text, crease_title));
4008        }
4009    });
4010    Some(creases)
4011}
4012
4013fn render_fold_icon_button(
4014    editor: WeakView<Editor>,
4015    icon: IconName,
4016    label: SharedString,
4017) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement> {
4018    Arc::new(move |fold_id, fold_range, _cx| {
4019        let editor = editor.clone();
4020        ButtonLike::new(fold_id)
4021            .style(ButtonStyle::Filled)
4022            .layer(ElevationIndex::ElevatedSurface)
4023            .child(Icon::new(icon))
4024            .child(Label::new(label.clone()).single_line())
4025            .on_click(move |_, cx| {
4026                editor
4027                    .update(cx, |editor, cx| {
4028                        let buffer_start = fold_range
4029                            .start
4030                            .to_point(&editor.buffer().read(cx).read(cx));
4031                        let buffer_row = MultiBufferRow(buffer_start.row);
4032                        editor.unfold_at(&UnfoldAt { buffer_row }, cx);
4033                    })
4034                    .ok();
4035            })
4036            .into_any_element()
4037    })
4038}
4039
4040#[derive(Debug, Clone, Serialize, Deserialize)]
4041struct CopyMetadata {
4042    creases: Vec<SelectedCreaseMetadata>,
4043}
4044
4045#[derive(Debug, Clone, Serialize, Deserialize)]
4046struct SelectedCreaseMetadata {
4047    range_relative_to_selection: Range<usize>,
4048    crease: CreaseMetadata,
4049}
4050
4051impl EventEmitter<EditorEvent> for ContextEditor {}
4052impl EventEmitter<SearchEvent> for ContextEditor {}
4053
4054impl Render for ContextEditor {
4055    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4056        let provider = LanguageModelRegistry::read_global(cx).active_provider();
4057        let accept_terms = if self.show_accept_terms {
4058            provider
4059                .as_ref()
4060                .and_then(|provider| provider.render_accept_terms(cx))
4061        } else {
4062            None
4063        };
4064
4065        v_flex()
4066            .key_context("ContextEditor")
4067            .capture_action(cx.listener(ContextEditor::cancel))
4068            .capture_action(cx.listener(ContextEditor::save))
4069            .capture_action(cx.listener(ContextEditor::copy))
4070            .capture_action(cx.listener(ContextEditor::cut))
4071            .capture_action(cx.listener(ContextEditor::paste))
4072            .capture_action(cx.listener(ContextEditor::cycle_message_role))
4073            .capture_action(cx.listener(ContextEditor::confirm_command))
4074            .on_action(cx.listener(ContextEditor::edit))
4075            .on_action(cx.listener(ContextEditor::assist))
4076            .on_action(cx.listener(ContextEditor::split))
4077            .size_full()
4078            .children(self.render_notice(cx))
4079            .child(
4080                div()
4081                    .flex_grow()
4082                    .bg(cx.theme().colors().editor_background)
4083                    .child(self.editor.clone()),
4084            )
4085            .when_some(accept_terms, |this, element| {
4086                this.child(
4087                    div()
4088                        .absolute()
4089                        .right_3()
4090                        .bottom_12()
4091                        .max_w_96()
4092                        .py_2()
4093                        .px_3()
4094                        .elevation_2(cx)
4095                        .bg(cx.theme().colors().surface_background)
4096                        .occlude()
4097                        .child(element),
4098                )
4099            })
4100            .children(self.render_last_error(cx))
4101            .child(
4102                h_flex().w_full().relative().child(
4103                    h_flex()
4104                        .p_2()
4105                        .w_full()
4106                        .border_t_1()
4107                        .border_color(cx.theme().colors().border_variant)
4108                        .bg(cx.theme().colors().editor_background)
4109                        .child(
4110                            h_flex()
4111                                .gap_1()
4112                                .child(render_inject_context_menu(cx.view().downgrade(), cx)),
4113                        )
4114                        .child(
4115                            h_flex()
4116                                .w_full()
4117                                .justify_end()
4118                                .when(
4119                                    AssistantSettings::get_global(cx).are_live_diffs_enabled(cx),
4120                                    |buttons| {
4121                                        buttons
4122                                            .items_center()
4123                                            .gap_1p5()
4124                                            .child(self.render_edit_button(cx))
4125                                            .child(
4126                                                Label::new("or")
4127                                                    .size(LabelSize::Small)
4128                                                    .color(Color::Muted),
4129                                            )
4130                                    },
4131                                )
4132                                .child(self.render_send_button(cx)),
4133                        ),
4134                ),
4135            )
4136    }
4137}
4138
4139impl FocusableView for ContextEditor {
4140    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4141        self.editor.focus_handle(cx)
4142    }
4143}
4144
4145impl Item for ContextEditor {
4146    type Event = editor::EditorEvent;
4147
4148    fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
4149        Some(util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into())
4150    }
4151
4152    fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
4153        match event {
4154            EditorEvent::Edited { .. } => {
4155                f(item::ItemEvent::Edit);
4156            }
4157            EditorEvent::TitleChanged => {
4158                f(item::ItemEvent::UpdateTab);
4159            }
4160            _ => {}
4161        }
4162    }
4163
4164    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
4165        Some(self.title(cx).to_string().into())
4166    }
4167
4168    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
4169        Some(Box::new(handle.clone()))
4170    }
4171
4172    fn set_nav_history(&mut self, nav_history: pane::ItemNavHistory, cx: &mut ViewContext<Self>) {
4173        self.editor.update(cx, |editor, cx| {
4174            Item::set_nav_history(editor, nav_history, cx)
4175        })
4176    }
4177
4178    fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
4179        self.editor
4180            .update(cx, |editor, cx| Item::navigate(editor, data, cx))
4181    }
4182
4183    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
4184        self.editor.update(cx, Item::deactivated)
4185    }
4186
4187    fn act_as_type<'a>(
4188        &'a self,
4189        type_id: TypeId,
4190        self_handle: &'a View<Self>,
4191        _: &'a AppContext,
4192    ) -> Option<AnyView> {
4193        if type_id == TypeId::of::<Self>() {
4194            Some(self_handle.to_any())
4195        } else if type_id == TypeId::of::<Editor>() {
4196            Some(self.editor.to_any())
4197        } else {
4198            None
4199        }
4200    }
4201}
4202
4203impl SearchableItem for ContextEditor {
4204    type Match = <Editor as SearchableItem>::Match;
4205
4206    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
4207        self.editor.update(cx, |editor, cx| {
4208            editor.clear_matches(cx);
4209        });
4210    }
4211
4212    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
4213        self.editor
4214            .update(cx, |editor, cx| editor.update_matches(matches, cx));
4215    }
4216
4217    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
4218        self.editor
4219            .update(cx, |editor, cx| editor.query_suggestion(cx))
4220    }
4221
4222    fn activate_match(
4223        &mut self,
4224        index: usize,
4225        matches: &[Self::Match],
4226        cx: &mut ViewContext<Self>,
4227    ) {
4228        self.editor.update(cx, |editor, cx| {
4229            editor.activate_match(index, matches, cx);
4230        });
4231    }
4232
4233    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
4234        self.editor
4235            .update(cx, |editor, cx| editor.select_matches(matches, cx));
4236    }
4237
4238    fn replace(
4239        &mut self,
4240        identifier: &Self::Match,
4241        query: &project::search::SearchQuery,
4242        cx: &mut ViewContext<Self>,
4243    ) {
4244        self.editor
4245            .update(cx, |editor, cx| editor.replace(identifier, query, cx));
4246    }
4247
4248    fn find_matches(
4249        &mut self,
4250        query: Arc<project::search::SearchQuery>,
4251        cx: &mut ViewContext<Self>,
4252    ) -> Task<Vec<Self::Match>> {
4253        self.editor
4254            .update(cx, |editor, cx| editor.find_matches(query, cx))
4255    }
4256
4257    fn active_match_index(
4258        &mut self,
4259        matches: &[Self::Match],
4260        cx: &mut ViewContext<Self>,
4261    ) -> Option<usize> {
4262        self.editor
4263            .update(cx, |editor, cx| editor.active_match_index(matches, cx))
4264    }
4265}
4266
4267impl FollowableItem for ContextEditor {
4268    fn remote_id(&self) -> Option<workspace::ViewId> {
4269        self.remote_id
4270    }
4271
4272    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
4273        let context = self.context.read(cx);
4274        Some(proto::view::Variant::ContextEditor(
4275            proto::view::ContextEditor {
4276                context_id: context.id().to_proto(),
4277                editor: if let Some(proto::view::Variant::Editor(proto)) =
4278                    self.editor.read(cx).to_state_proto(cx)
4279                {
4280                    Some(proto)
4281                } else {
4282                    None
4283                },
4284            },
4285        ))
4286    }
4287
4288    fn from_state_proto(
4289        workspace: View<Workspace>,
4290        id: workspace::ViewId,
4291        state: &mut Option<proto::view::Variant>,
4292        cx: &mut WindowContext,
4293    ) -> Option<Task<Result<View<Self>>>> {
4294        let proto::view::Variant::ContextEditor(_) = state.as_ref()? else {
4295            return None;
4296        };
4297        let Some(proto::view::Variant::ContextEditor(state)) = state.take() else {
4298            unreachable!()
4299        };
4300
4301        let context_id = ContextId::from_proto(state.context_id);
4302        let editor_state = state.editor?;
4303
4304        let (project, panel) = workspace.update(cx, |workspace, cx| {
4305            Some((
4306                workspace.project().clone(),
4307                workspace.panel::<AssistantPanel>(cx)?,
4308            ))
4309        })?;
4310
4311        let context_editor =
4312            panel.update(cx, |panel, cx| panel.open_remote_context(context_id, cx));
4313
4314        Some(cx.spawn(|mut cx| async move {
4315            let context_editor = context_editor.await?;
4316            context_editor
4317                .update(&mut cx, |context_editor, cx| {
4318                    context_editor.remote_id = Some(id);
4319                    context_editor.editor.update(cx, |editor, cx| {
4320                        editor.apply_update_proto(
4321                            &project,
4322                            proto::update_view::Variant::Editor(proto::update_view::Editor {
4323                                selections: editor_state.selections,
4324                                pending_selection: editor_state.pending_selection,
4325                                scroll_top_anchor: editor_state.scroll_top_anchor,
4326                                scroll_x: editor_state.scroll_y,
4327                                scroll_y: editor_state.scroll_y,
4328                                ..Default::default()
4329                            }),
4330                            cx,
4331                        )
4332                    })
4333                })?
4334                .await?;
4335            Ok(context_editor)
4336        }))
4337    }
4338
4339    fn to_follow_event(event: &Self::Event) -> Option<item::FollowEvent> {
4340        Editor::to_follow_event(event)
4341    }
4342
4343    fn add_event_to_update_proto(
4344        &self,
4345        event: &Self::Event,
4346        update: &mut Option<proto::update_view::Variant>,
4347        cx: &WindowContext,
4348    ) -> bool {
4349        self.editor
4350            .read(cx)
4351            .add_event_to_update_proto(event, update, cx)
4352    }
4353
4354    fn apply_update_proto(
4355        &mut self,
4356        project: &Model<Project>,
4357        message: proto::update_view::Variant,
4358        cx: &mut ViewContext<Self>,
4359    ) -> Task<Result<()>> {
4360        self.editor.update(cx, |editor, cx| {
4361            editor.apply_update_proto(project, message, cx)
4362        })
4363    }
4364
4365    fn is_project_item(&self, _cx: &WindowContext) -> bool {
4366        true
4367    }
4368
4369    fn set_leader_peer_id(
4370        &mut self,
4371        leader_peer_id: Option<proto::PeerId>,
4372        cx: &mut ViewContext<Self>,
4373    ) {
4374        self.editor.update(cx, |editor, cx| {
4375            editor.set_leader_peer_id(leader_peer_id, cx)
4376        })
4377    }
4378
4379    fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<item::Dedup> {
4380        if existing.context.read(cx).id() == self.context.read(cx).id() {
4381            Some(item::Dedup::KeepExisting)
4382        } else {
4383            None
4384        }
4385    }
4386}
4387
4388pub struct ContextEditorToolbarItem {
4389    fs: Arc<dyn Fs>,
4390    active_context_editor: Option<WeakView<ContextEditor>>,
4391    model_summary_editor: View<Editor>,
4392    model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
4393}
4394
4395fn render_inject_context_menu(
4396    active_context_editor: WeakView<ContextEditor>,
4397    cx: &mut WindowContext<'_>,
4398) -> impl IntoElement {
4399    let commands = SlashCommandRegistry::global(cx);
4400
4401    slash_command_picker::SlashCommandSelector::new(
4402        commands.clone(),
4403        active_context_editor,
4404        Button::new("trigger", "Add Context")
4405            .icon(IconName::Plus)
4406            .icon_size(IconSize::Small)
4407            .icon_color(Color::Muted)
4408            .icon_position(IconPosition::Start)
4409            .tooltip(|cx| Tooltip::text("Type / to insert via keyboard", cx)),
4410    )
4411}
4412
4413impl ContextEditorToolbarItem {
4414    pub fn new(
4415        workspace: &Workspace,
4416        model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
4417        model_summary_editor: View<Editor>,
4418    ) -> Self {
4419        Self {
4420            fs: workspace.app_state().fs.clone(),
4421            active_context_editor: None,
4422            model_summary_editor,
4423            model_selector_menu_handle,
4424        }
4425    }
4426
4427    fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
4428        let context = &self
4429            .active_context_editor
4430            .as_ref()?
4431            .upgrade()?
4432            .read(cx)
4433            .context;
4434        let (token_count_color, token_count, max_token_count) = match token_state(context, cx)? {
4435            TokenState::NoTokensLeft {
4436                max_token_count,
4437                token_count,
4438            } => (Color::Error, token_count, max_token_count),
4439            TokenState::HasMoreTokens {
4440                max_token_count,
4441                token_count,
4442                over_warn_threshold,
4443            } => {
4444                let color = if over_warn_threshold {
4445                    Color::Warning
4446                } else {
4447                    Color::Muted
4448                };
4449                (color, token_count, max_token_count)
4450            }
4451        };
4452        Some(
4453            h_flex()
4454                .gap_0p5()
4455                .child(
4456                    Label::new(humanize_token_count(token_count))
4457                        .size(LabelSize::Small)
4458                        .color(token_count_color),
4459                )
4460                .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
4461                .child(
4462                    Label::new(humanize_token_count(max_token_count))
4463                        .size(LabelSize::Small)
4464                        .color(Color::Muted),
4465                ),
4466        )
4467    }
4468}
4469
4470impl Render for ContextEditorToolbarItem {
4471    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4472        let left_side = h_flex()
4473            .group("chat-title-group")
4474            .pl_0p5()
4475            .gap_1()
4476            .items_center()
4477            .flex_grow()
4478            .child(
4479                div()
4480                    .w_full()
4481                    .when(self.active_context_editor.is_some(), |left_side| {
4482                        left_side.child(self.model_summary_editor.clone())
4483                    }),
4484            )
4485            .child(
4486                div().visible_on_hover("chat-title-group").child(
4487                    IconButton::new("regenerate-context", IconName::RefreshTitle)
4488                        .shape(ui::IconButtonShape::Square)
4489                        .tooltip(|cx| Tooltip::text("Regenerate Title", cx))
4490                        .on_click(cx.listener(move |_, _, cx| {
4491                            cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
4492                        })),
4493                ),
4494            );
4495        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
4496        let active_model = LanguageModelRegistry::read_global(cx).active_model();
4497        let right_side = h_flex()
4498            .gap_2()
4499            // TODO display this in a nicer way, once we have a design for it.
4500            // .children({
4501            //     let project = self
4502            //         .workspace
4503            //         .upgrade()
4504            //         .map(|workspace| workspace.read(cx).project().downgrade());
4505            //
4506            //     let scan_items_remaining = cx.update_global(|db: &mut SemanticDb, cx| {
4507            //         project.and_then(|project| db.remaining_summaries(&project, cx))
4508            //     });
4509            //     scan_items_remaining
4510            //         .map(|remaining_items| format!("Files to scan: {}", remaining_items))
4511            // })
4512            .child(
4513                ModelSelector::new(
4514                    self.fs.clone(),
4515                    ButtonLike::new("active-model")
4516                        .style(ButtonStyle::Subtle)
4517                        .child(
4518                            h_flex()
4519                                .w_full()
4520                                .gap_0p5()
4521                                .child(
4522                                    div()
4523                                        .overflow_x_hidden()
4524                                        .flex_grow()
4525                                        .whitespace_nowrap()
4526                                        .child(match (active_provider, active_model) {
4527                                            (Some(provider), Some(model)) => h_flex()
4528                                                .gap_1()
4529                                                .child(
4530                                                    Icon::new(
4531                                                        model
4532                                                            .icon()
4533                                                            .unwrap_or_else(|| provider.icon()),
4534                                                    )
4535                                                    .color(Color::Muted)
4536                                                    .size(IconSize::XSmall),
4537                                                )
4538                                                .child(
4539                                                    Label::new(model.name().0)
4540                                                        .size(LabelSize::Small)
4541                                                        .color(Color::Muted),
4542                                                )
4543                                                .into_any_element(),
4544                                            _ => Label::new("No model selected")
4545                                                .size(LabelSize::Small)
4546                                                .color(Color::Muted)
4547                                                .into_any_element(),
4548                                        }),
4549                                )
4550                                .child(
4551                                    Icon::new(IconName::ChevronDown)
4552                                        .color(Color::Muted)
4553                                        .size(IconSize::XSmall),
4554                                ),
4555                        )
4556                        .tooltip(move |cx| {
4557                            Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
4558                        }),
4559                )
4560                .with_handle(self.model_selector_menu_handle.clone()),
4561            )
4562            .children(self.render_remaining_tokens(cx));
4563
4564        h_flex()
4565            .size_full()
4566            .gap_2()
4567            .justify_between()
4568            .child(left_side)
4569            .child(right_side)
4570    }
4571}
4572
4573impl ToolbarItemView for ContextEditorToolbarItem {
4574    fn set_active_pane_item(
4575        &mut self,
4576        active_pane_item: Option<&dyn ItemHandle>,
4577        cx: &mut ViewContext<Self>,
4578    ) -> ToolbarItemLocation {
4579        self.active_context_editor = active_pane_item
4580            .and_then(|item| item.act_as::<ContextEditor>(cx))
4581            .map(|editor| editor.downgrade());
4582        cx.notify();
4583        if self.active_context_editor.is_none() {
4584            ToolbarItemLocation::Hidden
4585        } else {
4586            ToolbarItemLocation::PrimaryRight
4587        }
4588    }
4589
4590    fn pane_focus_update(&mut self, _pane_focused: bool, cx: &mut ViewContext<Self>) {
4591        cx.notify();
4592    }
4593}
4594
4595impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
4596
4597enum ContextEditorToolbarItemEvent {
4598    RegenerateSummary,
4599}
4600impl EventEmitter<ContextEditorToolbarItemEvent> for ContextEditorToolbarItem {}
4601
4602pub struct ContextHistory {
4603    picker: View<Picker<SavedContextPickerDelegate>>,
4604    _subscriptions: Vec<Subscription>,
4605    assistant_panel: WeakView<AssistantPanel>,
4606}
4607
4608impl ContextHistory {
4609    fn new(
4610        project: Model<Project>,
4611        context_store: Model<ContextStore>,
4612        assistant_panel: WeakView<AssistantPanel>,
4613        cx: &mut ViewContext<Self>,
4614    ) -> Self {
4615        let picker = cx.new_view(|cx| {
4616            Picker::uniform_list(
4617                SavedContextPickerDelegate::new(project, context_store.clone()),
4618                cx,
4619            )
4620            .modal(false)
4621            .max_height(None)
4622        });
4623
4624        let _subscriptions = vec![
4625            cx.observe(&context_store, |this, _, cx| {
4626                this.picker.update(cx, |picker, cx| picker.refresh(cx));
4627            }),
4628            cx.subscribe(&picker, Self::handle_picker_event),
4629        ];
4630
4631        Self {
4632            picker,
4633            _subscriptions,
4634            assistant_panel,
4635        }
4636    }
4637
4638    fn handle_picker_event(
4639        &mut self,
4640        _: View<Picker<SavedContextPickerDelegate>>,
4641        event: &SavedContextPickerEvent,
4642        cx: &mut ViewContext<Self>,
4643    ) {
4644        let SavedContextPickerEvent::Confirmed(context) = event;
4645        self.assistant_panel
4646            .update(cx, |assistant_panel, cx| match context {
4647                ContextMetadata::Remote(metadata) => {
4648                    assistant_panel
4649                        .open_remote_context(metadata.id.clone(), cx)
4650                        .detach_and_log_err(cx);
4651                }
4652                ContextMetadata::Saved(metadata) => {
4653                    assistant_panel
4654                        .open_saved_context(metadata.path.clone(), cx)
4655                        .detach_and_log_err(cx);
4656                }
4657            })
4658            .ok();
4659    }
4660}
4661
4662#[derive(Debug, PartialEq, Eq, Clone, Copy)]
4663pub enum WorkflowAssistStatus {
4664    Pending,
4665    Confirmed,
4666    Done,
4667    Idle,
4668}
4669
4670impl Render for ContextHistory {
4671    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
4672        div().size_full().child(self.picker.clone())
4673    }
4674}
4675
4676impl FocusableView for ContextHistory {
4677    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4678        self.picker.focus_handle(cx)
4679    }
4680}
4681
4682impl EventEmitter<()> for ContextHistory {}
4683
4684impl Item for ContextHistory {
4685    type Event = ();
4686
4687    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
4688        Some("History".into())
4689    }
4690}
4691
4692pub struct ConfigurationView {
4693    focus_handle: FocusHandle,
4694    configuration_views: HashMap<LanguageModelProviderId, AnyView>,
4695    _registry_subscription: Subscription,
4696}
4697
4698impl ConfigurationView {
4699    fn new(cx: &mut ViewContext<Self>) -> Self {
4700        let focus_handle = cx.focus_handle();
4701
4702        let registry_subscription = cx.subscribe(
4703            &LanguageModelRegistry::global(cx),
4704            |this, _, event: &language_model::Event, cx| match event {
4705                language_model::Event::AddedProvider(provider_id) => {
4706                    let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
4707                    if let Some(provider) = provider {
4708                        this.add_configuration_view(&provider, cx);
4709                    }
4710                }
4711                language_model::Event::RemovedProvider(provider_id) => {
4712                    this.remove_configuration_view(provider_id);
4713                }
4714                _ => {}
4715            },
4716        );
4717
4718        let mut this = Self {
4719            focus_handle,
4720            configuration_views: HashMap::default(),
4721            _registry_subscription: registry_subscription,
4722        };
4723        this.build_configuration_views(cx);
4724        this
4725    }
4726
4727    fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
4728        let providers = LanguageModelRegistry::read_global(cx).providers();
4729        for provider in providers {
4730            self.add_configuration_view(&provider, cx);
4731        }
4732    }
4733
4734    fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
4735        self.configuration_views.remove(provider_id);
4736    }
4737
4738    fn add_configuration_view(
4739        &mut self,
4740        provider: &Arc<dyn LanguageModelProvider>,
4741        cx: &mut ViewContext<Self>,
4742    ) {
4743        let configuration_view = provider.configuration_view(cx);
4744        self.configuration_views
4745            .insert(provider.id(), configuration_view);
4746    }
4747
4748    fn render_provider_view(
4749        &mut self,
4750        provider: &Arc<dyn LanguageModelProvider>,
4751        cx: &mut ViewContext<Self>,
4752    ) -> Div {
4753        let provider_id = provider.id().0.clone();
4754        let provider_name = provider.name().0.clone();
4755        let configuration_view = self.configuration_views.get(&provider.id()).cloned();
4756
4757        let open_new_context = cx.listener({
4758            let provider = provider.clone();
4759            move |_, _, cx| {
4760                cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
4761                    provider.clone(),
4762                ))
4763            }
4764        });
4765
4766        v_flex()
4767            .gap_2()
4768            .child(
4769                h_flex()
4770                    .justify_between()
4771                    .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
4772                    .when(provider.is_authenticated(cx), move |this| {
4773                        this.child(
4774                            h_flex().justify_end().child(
4775                                Button::new(
4776                                    SharedString::from(format!("new-context-{provider_id}")),
4777                                    "Open New Chat",
4778                                )
4779                                .icon_position(IconPosition::Start)
4780                                .icon(IconName::Plus)
4781                                .style(ButtonStyle::Filled)
4782                                .layer(ElevationIndex::ModalSurface)
4783                                .on_click(open_new_context),
4784                            ),
4785                        )
4786                    }),
4787            )
4788            .child(
4789                div()
4790                    .p(Spacing::Large.rems(cx))
4791                    .bg(cx.theme().colors().surface_background)
4792                    .border_1()
4793                    .border_color(cx.theme().colors().border_variant)
4794                    .rounded_md()
4795                    .when(configuration_view.is_none(), |this| {
4796                        this.child(div().child(Label::new(format!(
4797                            "No configuration view for {}",
4798                            provider_name
4799                        ))))
4800                    })
4801                    .when_some(configuration_view, |this, configuration_view| {
4802                        this.child(configuration_view)
4803                    }),
4804            )
4805    }
4806}
4807
4808impl Render for ConfigurationView {
4809    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4810        let providers = LanguageModelRegistry::read_global(cx).providers();
4811        let provider_views = providers
4812            .into_iter()
4813            .map(|provider| self.render_provider_view(&provider, cx))
4814            .collect::<Vec<_>>();
4815
4816        let mut element = v_flex()
4817            .id("assistant-configuration-view")
4818            .track_focus(&self.focus_handle(cx))
4819            .bg(cx.theme().colors().editor_background)
4820            .size_full()
4821            .overflow_y_scroll()
4822            .child(
4823                v_flex()
4824                    .p(Spacing::XXLarge.rems(cx))
4825                    .border_b_1()
4826                    .border_color(cx.theme().colors().border)
4827                    .gap_1()
4828                    .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
4829                    .child(
4830                        Label::new(
4831                            "At least one LLM provider must be configured to use the Assistant.",
4832                        )
4833                        .color(Color::Muted),
4834                    ),
4835            )
4836            .child(
4837                v_flex()
4838                    .p(Spacing::XXLarge.rems(cx))
4839                    .mt_1()
4840                    .gap_6()
4841                    .flex_1()
4842                    .children(provider_views),
4843            )
4844            .into_any();
4845
4846        // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
4847        // because we couldn't the element to take up the size of the parent.
4848        canvas(
4849            move |bounds, cx| {
4850                element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
4851                element
4852            },
4853            |_, mut element, cx| {
4854                element.paint(cx);
4855            },
4856        )
4857        .flex_1()
4858        .w_full()
4859    }
4860}
4861
4862pub enum ConfigurationViewEvent {
4863    NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
4864}
4865
4866impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
4867
4868impl FocusableView for ConfigurationView {
4869    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
4870        self.focus_handle.clone()
4871    }
4872}
4873
4874impl Item for ConfigurationView {
4875    type Event = ConfigurationViewEvent;
4876
4877    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
4878        Some("Configuration".into())
4879    }
4880}
4881
4882type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
4883
4884fn render_slash_command_output_toggle(
4885    row: MultiBufferRow,
4886    is_folded: bool,
4887    fold: ToggleFold,
4888    _cx: &mut WindowContext,
4889) -> AnyElement {
4890    Disclosure::new(
4891        ("slash-command-output-fold-indicator", row.0 as u64),
4892        !is_folded,
4893    )
4894    .selected(is_folded)
4895    .on_click(move |_e, cx| fold(!is_folded, cx))
4896    .into_any_element()
4897}
4898
4899fn fold_toggle(
4900    name: &'static str,
4901) -> impl Fn(
4902    MultiBufferRow,
4903    bool,
4904    Arc<dyn Fn(bool, &mut WindowContext<'_>) + Send + Sync>,
4905    &mut WindowContext<'_>,
4906) -> AnyElement {
4907    move |row, is_folded, fold, _cx| {
4908        Disclosure::new((name, row.0 as u64), !is_folded)
4909            .selected(is_folded)
4910            .on_click(move |_e, cx| fold(!is_folded, cx))
4911            .into_any_element()
4912    }
4913}
4914
4915fn quote_selection_fold_placeholder(title: String, editor: WeakView<Editor>) -> FoldPlaceholder {
4916    FoldPlaceholder {
4917        render: Arc::new({
4918            move |fold_id, fold_range, _cx| {
4919                let editor = editor.clone();
4920                ButtonLike::new(fold_id)
4921                    .style(ButtonStyle::Filled)
4922                    .layer(ElevationIndex::ElevatedSurface)
4923                    .child(Icon::new(IconName::TextSnippet))
4924                    .child(Label::new(title.clone()).single_line())
4925                    .on_click(move |_, cx| {
4926                        editor
4927                            .update(cx, |editor, cx| {
4928                                let buffer_start = fold_range
4929                                    .start
4930                                    .to_point(&editor.buffer().read(cx).read(cx));
4931                                let buffer_row = MultiBufferRow(buffer_start.row);
4932                                editor.unfold_at(&UnfoldAt { buffer_row }, cx);
4933                            })
4934                            .ok();
4935                    })
4936                    .into_any_element()
4937            }
4938        }),
4939        constrain_width: false,
4940        merge_adjacent: false,
4941    }
4942}
4943
4944fn render_quote_selection_output_toggle(
4945    row: MultiBufferRow,
4946    is_folded: bool,
4947    fold: ToggleFold,
4948    _cx: &mut WindowContext,
4949) -> AnyElement {
4950    Disclosure::new(("quote-selection-indicator", row.0 as u64), !is_folded)
4951        .selected(is_folded)
4952        .on_click(move |_e, cx| fold(!is_folded, cx))
4953        .into_any_element()
4954}
4955
4956fn render_pending_slash_command_gutter_decoration(
4957    row: MultiBufferRow,
4958    status: &PendingSlashCommandStatus,
4959    confirm_command: Arc<dyn Fn(&mut WindowContext)>,
4960) -> AnyElement {
4961    let mut icon = IconButton::new(
4962        ("slash-command-gutter-decoration", row.0),
4963        ui::IconName::TriangleRight,
4964    )
4965    .on_click(move |_e, cx| confirm_command(cx))
4966    .icon_size(ui::IconSize::Small)
4967    .size(ui::ButtonSize::None);
4968
4969    match status {
4970        PendingSlashCommandStatus::Idle => {
4971            icon = icon.icon_color(Color::Muted);
4972        }
4973        PendingSlashCommandStatus::Running { .. } => {
4974            icon = icon.selected(true);
4975        }
4976        PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
4977    }
4978
4979    icon.into_any_element()
4980}
4981
4982fn render_docs_slash_command_trailer(
4983    row: MultiBufferRow,
4984    command: PendingSlashCommand,
4985    cx: &mut WindowContext,
4986) -> AnyElement {
4987    if command.arguments.is_empty() {
4988        return Empty.into_any();
4989    }
4990    let args = DocsSlashCommandArgs::parse(&command.arguments);
4991
4992    let Some(store) = args
4993        .provider()
4994        .and_then(|provider| IndexedDocsStore::try_global(provider, cx).ok())
4995    else {
4996        return Empty.into_any();
4997    };
4998
4999    let Some(package) = args.package() else {
5000        return Empty.into_any();
5001    };
5002
5003    let mut children = Vec::new();
5004
5005    if store.is_indexing(&package) {
5006        children.push(
5007            div()
5008                .id(("crates-being-indexed", row.0))
5009                .child(Icon::new(IconName::ArrowCircle).with_animation(
5010                    "arrow-circle",
5011                    Animation::new(Duration::from_secs(4)).repeat(),
5012                    |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
5013                ))
5014                .tooltip({
5015                    let package = package.clone();
5016                    move |cx| Tooltip::text(format!("Indexing {package}"), cx)
5017                })
5018                .into_any_element(),
5019        );
5020    }
5021
5022    if let Some(latest_error) = store.latest_error_for_package(&package) {
5023        children.push(
5024            div()
5025                .id(("latest-error", row.0))
5026                .child(
5027                    Icon::new(IconName::Warning)
5028                        .size(IconSize::Small)
5029                        .color(Color::Warning),
5030                )
5031                .tooltip(move |cx| Tooltip::text(format!("Failed to index: {latest_error}"), cx))
5032                .into_any_element(),
5033        )
5034    }
5035
5036    let is_indexing = store.is_indexing(&package);
5037    let latest_error = store.latest_error_for_package(&package);
5038
5039    if !is_indexing && latest_error.is_none() {
5040        return Empty.into_any();
5041    }
5042
5043    h_flex().gap_2().children(children).into_any_element()
5044}
5045
5046fn make_lsp_adapter_delegate(
5047    project: &Model<Project>,
5048    cx: &mut AppContext,
5049) -> Result<Option<Arc<dyn LspAdapterDelegate>>> {
5050    project.update(cx, |project, cx| {
5051        // TODO: Find the right worktree.
5052        let Some(worktree) = project.worktrees(cx).next() else {
5053            return Ok(None::<Arc<dyn LspAdapterDelegate>>);
5054        };
5055        let http_client = project.client().http_client().clone();
5056        project.lsp_store().update(cx, |lsp_store, cx| {
5057            Ok(Some(LocalLspAdapterDelegate::new(
5058                lsp_store,
5059                &worktree,
5060                http_client,
5061                project.fs().clone(),
5062                cx,
5063            ) as Arc<dyn LspAdapterDelegate>))
5064        })
5065    })
5066}
5067
5068fn slash_command_error_block_renderer(message: String) -> RenderBlock {
5069    Box::new(move |_| {
5070        div()
5071            .pl_6()
5072            .child(
5073                Label::new(format!("error: {}", message))
5074                    .single_line()
5075                    .color(Color::Error),
5076            )
5077            .into_any()
5078    })
5079}
5080
5081enum TokenState {
5082    NoTokensLeft {
5083        max_token_count: usize,
5084        token_count: usize,
5085    },
5086    HasMoreTokens {
5087        max_token_count: usize,
5088        token_count: usize,
5089        over_warn_threshold: bool,
5090    },
5091}
5092
5093fn token_state(context: &Model<Context>, cx: &AppContext) -> Option<TokenState> {
5094    const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
5095
5096    let model = LanguageModelRegistry::read_global(cx).active_model()?;
5097    let token_count = context.read(cx).token_count()?;
5098    let max_token_count = model.max_token_count();
5099
5100    let remaining_tokens = max_token_count as isize - token_count as isize;
5101    let token_state = if remaining_tokens <= 0 {
5102        TokenState::NoTokensLeft {
5103            max_token_count,
5104            token_count,
5105        }
5106    } else {
5107        let over_warn_threshold =
5108            token_count as f32 / max_token_count as f32 >= WARNING_TOKEN_THRESHOLD;
5109        TokenState::HasMoreTokens {
5110            max_token_count,
5111            token_count,
5112            over_warn_threshold,
5113        }
5114    };
5115    Some(token_state)
5116}
5117
5118fn size_for_image(data: &RenderImage, max_size: Size<Pixels>) -> Size<Pixels> {
5119    let image_size = data
5120        .size(0)
5121        .map(|dimension| Pixels::from(u32::from(dimension)));
5122    let image_ratio = image_size.width / image_size.height;
5123    let bounds_ratio = max_size.width / max_size.height;
5124
5125    if image_size.width > max_size.width || image_size.height > max_size.height {
5126        if bounds_ratio > image_ratio {
5127            size(
5128                image_size.width * (max_size.height / image_size.height),
5129                max_size.height,
5130            )
5131        } else {
5132            size(
5133                max_size.width,
5134                image_size.height * (max_size.width / image_size.width),
5135            )
5136        }
5137    } else {
5138        size(image_size.width, image_size.height)
5139    }
5140}
5141
5142enum ConfigurationError {
5143    NoProvider,
5144    ProviderNotAuthenticated,
5145}
5146
5147fn configuration_error(cx: &AppContext) -> Option<ConfigurationError> {
5148    let provider = LanguageModelRegistry::read_global(cx).active_provider();
5149    let is_authenticated = provider
5150        .as_ref()
5151        .map_or(false, |provider| provider.is_authenticated(cx));
5152
5153    if provider.is_some() && is_authenticated {
5154        return None;
5155    }
5156
5157    if provider.is_none() {
5158        return Some(ConfigurationError::NoProvider);
5159    }
5160
5161    if !is_authenticated {
5162        return Some(ConfigurationError::ProviderNotAuthenticated);
5163    }
5164
5165    None
5166}
5167
5168#[cfg(test)]
5169mod tests {
5170    use super::*;
5171    use gpui::{AppContext, Context};
5172    use language::Buffer;
5173    use unindent::Unindent;
5174
5175    #[gpui::test]
5176    fn test_find_code_blocks(cx: &mut AppContext) {
5177        let markdown = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
5178
5179        let buffer = cx.new_model(|cx| {
5180            let text = r#"
5181                line 0
5182                line 1
5183                ```rust
5184                fn main() {}
5185                ```
5186                line 5
5187                line 6
5188                line 7
5189                ```go
5190                func main() {}
5191                ```
5192                line 11
5193                ```
5194                this is plain text code block
5195                ```
5196
5197                ```go
5198                func another() {}
5199                ```
5200                line 19
5201            "#
5202            .unindent();
5203            let mut buffer = Buffer::local(text, cx);
5204            buffer.set_language(Some(markdown.clone()), cx);
5205            buffer
5206        });
5207        let snapshot = buffer.read(cx).snapshot();
5208
5209        let code_blocks = vec![
5210            Point::new(3, 0)..Point::new(4, 0),
5211            Point::new(9, 0)..Point::new(10, 0),
5212            Point::new(13, 0)..Point::new(14, 0),
5213            Point::new(17, 0)..Point::new(18, 0),
5214        ]
5215        .into_iter()
5216        .map(|range| snapshot.point_to_offset(range.start)..snapshot.point_to_offset(range.end))
5217        .collect::<Vec<_>>();
5218
5219        let expected_results = vec![
5220            (0, None),
5221            (1, None),
5222            (2, Some(code_blocks[0].clone())),
5223            (3, Some(code_blocks[0].clone())),
5224            (4, Some(code_blocks[0].clone())),
5225            (5, None),
5226            (6, None),
5227            (7, None),
5228            (8, Some(code_blocks[1].clone())),
5229            (9, Some(code_blocks[1].clone())),
5230            (10, Some(code_blocks[1].clone())),
5231            (11, None),
5232            (12, Some(code_blocks[2].clone())),
5233            (13, Some(code_blocks[2].clone())),
5234            (14, Some(code_blocks[2].clone())),
5235            (15, None),
5236            (16, Some(code_blocks[3].clone())),
5237            (17, Some(code_blocks[3].clone())),
5238            (18, Some(code_blocks[3].clone())),
5239            (19, None),
5240        ];
5241
5242        for (row, expected) in expected_results {
5243            let offset = snapshot.point_to_offset(Point::new(row, 0));
5244            let range = find_surrounding_code_block(&snapshot, offset);
5245            assert_eq!(range, expected, "unexpected result on row {:?}", row);
5246        }
5247    }
5248}