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 esc_kbd(cx: &WindowContext) -> Div {
2583        let colors = cx.theme().colors().clone();
2584
2585        h_flex()
2586            .items_center()
2587            .gap_1()
2588            .font(theme::ThemeSettings::get_global(cx).buffer_font.clone())
2589            .text_size(TextSize::XSmall.rems(cx))
2590            .text_color(colors.text_muted)
2591            .child("Press")
2592            .child(
2593                h_flex()
2594                    .rounded_md()
2595                    .px_1()
2596                    .mr_0p5()
2597                    .border_1()
2598                    .border_color(theme::color_alpha(colors.border_variant, 0.6))
2599                    .bg(theme::color_alpha(colors.element_background, 0.6))
2600                    .child("esc"),
2601            )
2602            .child("to cancel")
2603    }
2604
2605    fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
2606        self.editor.update(cx, |editor, cx| {
2607            let buffer = editor.buffer().read(cx).snapshot(cx);
2608
2609            let excerpt_id = *buffer.as_singleton().unwrap().0;
2610            let mut old_blocks = std::mem::take(&mut self.blocks);
2611            let mut blocks_to_remove: HashMap<_, _> = old_blocks
2612                .iter()
2613                .map(|(message_id, (_, block_id))| (*message_id, *block_id))
2614                .collect();
2615            let mut blocks_to_replace: HashMap<_, RenderBlock> = Default::default();
2616
2617            let render_block = |message: MessageMetadata| -> RenderBlock {
2618                Box::new({
2619                    let context = self.context.clone();
2620
2621                    move |cx| {
2622                        let message_id = MessageId(message.timestamp);
2623                        let llm_loading = message.role == Role::Assistant
2624                            && message.status == MessageStatus::Pending;
2625
2626                        let (label, spinner, note) = match message.role {
2627                            Role::User => (
2628                                Label::new("You").color(Color::Default).into_any_element(),
2629                                None,
2630                                None,
2631                            ),
2632                            Role::Assistant => {
2633                                let base_label = Label::new("Assistant").color(Color::Info);
2634                                let mut spinner = None;
2635                                let mut note = None;
2636                                let animated_label = if llm_loading {
2637                                    base_label
2638                                        .with_animation(
2639                                            "pulsating-label",
2640                                            Animation::new(Duration::from_secs(2))
2641                                                .repeat()
2642                                                .with_easing(pulsating_between(0.4, 0.8)),
2643                                            |label, delta| label.alpha(delta),
2644                                        )
2645                                        .into_any_element()
2646                                } else {
2647                                    base_label.into_any_element()
2648                                };
2649                                if llm_loading {
2650                                    spinner = Some(
2651                                        Icon::new(IconName::ArrowCircle)
2652                                            .size(IconSize::XSmall)
2653                                            .color(Color::Info)
2654                                            .with_animation(
2655                                                "arrow-circle",
2656                                                Animation::new(Duration::from_secs(2)).repeat(),
2657                                                |icon, delta| {
2658                                                    icon.transform(Transformation::rotate(
2659                                                        percentage(delta),
2660                                                    ))
2661                                                },
2662                                            )
2663                                            .into_any_element(),
2664                                    );
2665                                    note = Some(Self::esc_kbd(cx).into_any_element());
2666                                }
2667                                (animated_label, spinner, note)
2668                            }
2669                            Role::System => (
2670                                Label::new("System")
2671                                    .color(Color::Warning)
2672                                    .into_any_element(),
2673                                None,
2674                                None,
2675                            ),
2676                        };
2677
2678                        let sender = h_flex()
2679                            .items_center()
2680                            .gap_2p5()
2681                            .child(
2682                                ButtonLike::new("role")
2683                                    .style(ButtonStyle::Filled)
2684                                    .child(
2685                                        h_flex()
2686                                            .items_center()
2687                                            .gap_1p5()
2688                                            .child(label)
2689                                            .children(spinner),
2690                                    )
2691                                    .tooltip(|cx| {
2692                                        Tooltip::with_meta(
2693                                            "Toggle message role",
2694                                            None,
2695                                            "Available roles: You (User), Assistant, System",
2696                                            cx,
2697                                        )
2698                                    })
2699                                    .on_click({
2700                                        let context = context.clone();
2701                                        move |_, cx| {
2702                                            context.update(cx, |context, cx| {
2703                                                context.cycle_message_roles(
2704                                                    HashSet::from_iter(Some(message_id)),
2705                                                    cx,
2706                                                )
2707                                            })
2708                                        }
2709                                    }),
2710                            )
2711                            .children(note);
2712
2713                        h_flex()
2714                            .id(("message_header", message_id.as_u64()))
2715                            .pl(cx.gutter_dimensions.full_width())
2716                            .h_11()
2717                            .w_full()
2718                            .relative()
2719                            .gap_1()
2720                            .child(sender)
2721                            .children(match &message.cache {
2722                                Some(cache) if cache.is_final_anchor => match cache.status {
2723                                    CacheStatus::Cached => Some(
2724                                        div()
2725                                            .id("cached")
2726                                            .child(
2727                                                Icon::new(IconName::DatabaseZap)
2728                                                    .size(IconSize::XSmall)
2729                                                    .color(Color::Hint),
2730                                            )
2731                                            .tooltip(|cx| {
2732                                                Tooltip::with_meta(
2733                                                    "Context cached",
2734                                                    None,
2735                                                    "Large messages cached to optimize performance",
2736                                                    cx,
2737                                                )
2738                                            })
2739                                            .into_any_element(),
2740                                    ),
2741                                    CacheStatus::Pending => Some(
2742                                        div()
2743                                            .child(
2744                                                Icon::new(IconName::Ellipsis)
2745                                                    .size(IconSize::XSmall)
2746                                                    .color(Color::Hint),
2747                                            )
2748                                            .into_any_element(),
2749                                    ),
2750                                },
2751                                _ => None,
2752                            })
2753                            .children(match &message.status {
2754                                MessageStatus::Error(error) => Some(
2755                                    Button::new("show-error", "Error")
2756                                        .color(Color::Error)
2757                                        .selected_label_color(Color::Error)
2758                                        .selected_icon_color(Color::Error)
2759                                        .icon(IconName::XCircle)
2760                                        .icon_color(Color::Error)
2761                                        .icon_size(IconSize::Small)
2762                                        .icon_position(IconPosition::Start)
2763                                        .tooltip(move |cx| {
2764                                            Tooltip::with_meta(
2765                                                "Error interacting with language model",
2766                                                None,
2767                                                "Click for more details",
2768                                                cx,
2769                                            )
2770                                        })
2771                                        .on_click({
2772                                            let context = context.clone();
2773                                            let error = error.clone();
2774                                            move |_, cx| {
2775                                                context.update(cx, |_, cx| {
2776                                                    cx.emit(ContextEvent::ShowAssistError(
2777                                                        error.clone(),
2778                                                    ));
2779                                                });
2780                                            }
2781                                        })
2782                                        .into_any_element(),
2783                                ),
2784                                MessageStatus::Canceled => Some(
2785                                    ButtonLike::new("canceled")
2786                                        .child(Icon::new(IconName::XCircle).color(Color::Disabled))
2787                                        .child(
2788                                            Label::new("Canceled")
2789                                                .size(LabelSize::Small)
2790                                                .color(Color::Disabled),
2791                                        )
2792                                        .tooltip(move |cx| {
2793                                            Tooltip::with_meta(
2794                                                "Canceled",
2795                                                None,
2796                                                "Interaction with the assistant was canceled",
2797                                                cx,
2798                                            )
2799                                        })
2800                                        .into_any_element(),
2801                                ),
2802                                _ => None,
2803                            })
2804                            .into_any_element()
2805                    }
2806                })
2807            };
2808            let create_block_properties = |message: &Message| BlockProperties {
2809                height: 2,
2810                style: BlockStyle::Sticky,
2811                placement: BlockPlacement::Above(
2812                    buffer
2813                        .anchor_in_excerpt(excerpt_id, message.anchor_range.start)
2814                        .unwrap(),
2815                ),
2816                priority: usize::MAX,
2817                render: render_block(MessageMetadata::from(message)),
2818            };
2819            let mut new_blocks = vec![];
2820            let mut block_index_to_message = vec![];
2821            for message in self.context.read(cx).messages(cx) {
2822                if let Some(_) = blocks_to_remove.remove(&message.id) {
2823                    // This is an old message that we might modify.
2824                    let Some((meta, block_id)) = old_blocks.get_mut(&message.id) else {
2825                        debug_assert!(
2826                            false,
2827                            "old_blocks should contain a message_id we've just removed."
2828                        );
2829                        continue;
2830                    };
2831                    // Should we modify it?
2832                    let message_meta = MessageMetadata::from(&message);
2833                    if meta != &message_meta {
2834                        blocks_to_replace.insert(*block_id, render_block(message_meta.clone()));
2835                        *meta = message_meta;
2836                    }
2837                } else {
2838                    // This is a new message.
2839                    new_blocks.push(create_block_properties(&message));
2840                    block_index_to_message.push((message.id, MessageMetadata::from(&message)));
2841                }
2842            }
2843            editor.replace_blocks(blocks_to_replace, None, cx);
2844            editor.remove_blocks(blocks_to_remove.into_values().collect(), None, cx);
2845
2846            let ids = editor.insert_blocks(new_blocks, None, cx);
2847            old_blocks.extend(ids.into_iter().zip(block_index_to_message).map(
2848                |(block_id, (message_id, message_meta))| (message_id, (message_meta, block_id)),
2849            ));
2850            self.blocks = old_blocks;
2851        });
2852    }
2853
2854    /// Returns either the selected text, or the content of the Markdown code
2855    /// block surrounding the cursor.
2856    fn get_selection_or_code_block(
2857        context_editor_view: &View<ContextEditor>,
2858        cx: &mut ViewContext<Workspace>,
2859    ) -> Option<(String, bool)> {
2860        const CODE_FENCE_DELIMITER: &'static str = "```";
2861
2862        let context_editor = context_editor_view.read(cx).editor.clone();
2863        context_editor.update(cx, |context_editor, cx| {
2864            if context_editor.selections.newest::<Point>(cx).is_empty() {
2865                let snapshot = context_editor.buffer().read(cx).snapshot(cx);
2866                let (_, _, snapshot) = snapshot.as_singleton()?;
2867
2868                let head = context_editor.selections.newest::<Point>(cx).head();
2869                let offset = snapshot.point_to_offset(head);
2870
2871                let surrounding_code_block_range = find_surrounding_code_block(snapshot, offset)?;
2872                let mut text = snapshot
2873                    .text_for_range(surrounding_code_block_range)
2874                    .collect::<String>();
2875
2876                // If there is no newline trailing the closing three-backticks, then
2877                // tree-sitter-md extends the range of the content node to include
2878                // the backticks.
2879                if text.ends_with(CODE_FENCE_DELIMITER) {
2880                    text.drain((text.len() - CODE_FENCE_DELIMITER.len())..);
2881                }
2882
2883                (!text.is_empty()).then_some((text, true))
2884            } else {
2885                let anchor = context_editor.selections.newest_anchor();
2886                let text = context_editor
2887                    .buffer()
2888                    .read(cx)
2889                    .read(cx)
2890                    .text_for_range(anchor.range())
2891                    .collect::<String>();
2892
2893                (!text.is_empty()).then_some((text, false))
2894            }
2895        })
2896    }
2897
2898    fn insert_selection(
2899        workspace: &mut Workspace,
2900        _: &InsertIntoEditor,
2901        cx: &mut ViewContext<Workspace>,
2902    ) {
2903        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2904            return;
2905        };
2906        let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
2907            return;
2908        };
2909        let Some(active_editor_view) = workspace
2910            .active_item(cx)
2911            .and_then(|item| item.act_as::<Editor>(cx))
2912        else {
2913            return;
2914        };
2915
2916        if let Some((text, _)) = Self::get_selection_or_code_block(&context_editor_view, cx) {
2917            active_editor_view.update(cx, |editor, cx| {
2918                editor.insert(&text, cx);
2919                editor.focus(cx);
2920            })
2921        }
2922    }
2923
2924    fn copy_code(workspace: &mut Workspace, _: &CopyCode, cx: &mut ViewContext<Workspace>) {
2925        let result = maybe!({
2926            let panel = workspace.panel::<AssistantPanel>(cx)?;
2927            let context_editor_view = panel.read(cx).active_context_editor(cx)?;
2928            Self::get_selection_or_code_block(&context_editor_view, cx)
2929        });
2930        let Some((text, is_code_block)) = result else {
2931            return;
2932        };
2933
2934        cx.write_to_clipboard(ClipboardItem::new_string(text));
2935
2936        struct CopyToClipboardToast;
2937        workspace.show_toast(
2938            Toast::new(
2939                NotificationId::unique::<CopyToClipboardToast>(),
2940                format!(
2941                    "{} copied to clipboard.",
2942                    if is_code_block {
2943                        "Code block"
2944                    } else {
2945                        "Selection"
2946                    }
2947                ),
2948            )
2949            .autohide(),
2950            cx,
2951        );
2952    }
2953
2954    fn insert_dragged_files(
2955        workspace: &mut Workspace,
2956        action: &InsertDraggedFiles,
2957        cx: &mut ViewContext<Workspace>,
2958    ) {
2959        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2960            return;
2961        };
2962        let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
2963            return;
2964        };
2965
2966        let project = workspace.project().clone();
2967
2968        let paths = match action {
2969            InsertDraggedFiles::ProjectPaths(paths) => Task::ready((paths.clone(), vec![])),
2970            InsertDraggedFiles::ExternalFiles(paths) => {
2971                let tasks = paths
2972                    .clone()
2973                    .into_iter()
2974                    .map(|path| Workspace::project_path_for_path(project.clone(), &path, false, cx))
2975                    .collect::<Vec<_>>();
2976
2977                cx.spawn(move |_, cx| async move {
2978                    let mut paths = vec![];
2979                    let mut worktrees = vec![];
2980
2981                    let opened_paths = futures::future::join_all(tasks).await;
2982                    for (worktree, project_path) in opened_paths.into_iter().flatten() {
2983                        let Ok(worktree_root_name) =
2984                            worktree.read_with(&cx, |worktree, _| worktree.root_name().to_string())
2985                        else {
2986                            continue;
2987                        };
2988
2989                        let mut full_path = PathBuf::from(worktree_root_name.clone());
2990                        full_path.push(&project_path.path);
2991                        paths.push(full_path);
2992                        worktrees.push(worktree);
2993                    }
2994
2995                    (paths, worktrees)
2996                })
2997            }
2998        };
2999
3000        cx.spawn(|_, mut cx| async move {
3001            let (paths, dragged_file_worktrees) = paths.await;
3002            let cmd_name = file_command::FileSlashCommand.name();
3003
3004            context_editor_view
3005                .update(&mut cx, |context_editor, cx| {
3006                    let file_argument = paths
3007                        .into_iter()
3008                        .map(|path| path.to_string_lossy().to_string())
3009                        .collect::<Vec<_>>()
3010                        .join(" ");
3011
3012                    context_editor.editor.update(cx, |editor, cx| {
3013                        editor.insert("\n", cx);
3014                        editor.insert(&format!("/{} {}", cmd_name, file_argument), cx);
3015                    });
3016
3017                    context_editor.confirm_command(&ConfirmCommand, cx);
3018
3019                    context_editor
3020                        .dragged_file_worktrees
3021                        .extend(dragged_file_worktrees);
3022                })
3023                .log_err();
3024        })
3025        .detach();
3026    }
3027
3028    fn quote_selection(
3029        workspace: &mut Workspace,
3030        _: &QuoteSelection,
3031        cx: &mut ViewContext<Workspace>,
3032    ) {
3033        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3034            return;
3035        };
3036
3037        let Some(creases) = selections_creases(workspace, cx) else {
3038            return;
3039        };
3040
3041        if creases.is_empty() {
3042            return;
3043        }
3044        // Activate the panel
3045        if !panel.focus_handle(cx).contains_focused(cx) {
3046            workspace.toggle_panel_focus::<AssistantPanel>(cx);
3047        }
3048
3049        panel.update(cx, |_, cx| {
3050            // Wait to create a new context until the workspace is no longer
3051            // being updated.
3052            cx.defer(move |panel, cx| {
3053                if let Some(context) = panel
3054                    .active_context_editor(cx)
3055                    .or_else(|| panel.new_context(cx))
3056                {
3057                    context.update(cx, |context, cx| {
3058                        context.editor.update(cx, |editor, cx| {
3059                            editor.insert("\n", cx);
3060                            for (text, crease_title) in creases {
3061                                let point = editor.selections.newest::<Point>(cx).head();
3062                                let start_row = MultiBufferRow(point.row);
3063
3064                                editor.insert(&text, cx);
3065
3066                                let snapshot = editor.buffer().read(cx).snapshot(cx);
3067                                let anchor_before = snapshot.anchor_after(point);
3068                                let anchor_after = editor
3069                                    .selections
3070                                    .newest_anchor()
3071                                    .head()
3072                                    .bias_left(&snapshot);
3073
3074                                editor.insert("\n", cx);
3075
3076                                let fold_placeholder = quote_selection_fold_placeholder(
3077                                    crease_title,
3078                                    cx.view().downgrade(),
3079                                );
3080                                let crease = Crease::new(
3081                                    anchor_before..anchor_after,
3082                                    fold_placeholder,
3083                                    render_quote_selection_output_toggle,
3084                                    |_, _, _| Empty.into_any(),
3085                                );
3086                                editor.insert_creases(vec![crease], cx);
3087                                editor.fold_at(
3088                                    &FoldAt {
3089                                        buffer_row: start_row,
3090                                    },
3091                                    cx,
3092                                );
3093                            }
3094                        })
3095                    });
3096                };
3097            });
3098        });
3099    }
3100
3101    fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
3102        if self.editor.read(cx).selections.count() == 1 {
3103            let (copied_text, metadata, _) = self.get_clipboard_contents(cx);
3104            cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
3105                copied_text,
3106                metadata,
3107            ));
3108            cx.stop_propagation();
3109            return;
3110        }
3111
3112        cx.propagate();
3113    }
3114
3115    fn cut(&mut self, _: &editor::actions::Cut, cx: &mut ViewContext<Self>) {
3116        if self.editor.read(cx).selections.count() == 1 {
3117            let (copied_text, metadata, selections) = self.get_clipboard_contents(cx);
3118
3119            self.editor.update(cx, |editor, cx| {
3120                editor.transact(cx, |this, cx| {
3121                    this.change_selections(Some(Autoscroll::fit()), cx, |s| {
3122                        s.select(selections);
3123                    });
3124                    this.insert("", cx);
3125                    cx.write_to_clipboard(ClipboardItem::new_string_with_json_metadata(
3126                        copied_text,
3127                        metadata,
3128                    ));
3129                });
3130            });
3131
3132            cx.stop_propagation();
3133            return;
3134        }
3135
3136        cx.propagate();
3137    }
3138
3139    fn get_clipboard_contents(
3140        &mut self,
3141        cx: &mut ViewContext<Self>,
3142    ) -> (String, CopyMetadata, Vec<text::Selection<usize>>) {
3143        let (snapshot, selection, creases) = self.editor.update(cx, |editor, cx| {
3144            let mut selection = editor.selections.newest::<Point>(cx);
3145            let snapshot = editor.buffer().read(cx).snapshot(cx);
3146
3147            let is_entire_line = selection.is_empty() || editor.selections.line_mode;
3148            if is_entire_line {
3149                selection.start = Point::new(selection.start.row, 0);
3150                selection.end =
3151                    cmp::min(snapshot.max_point(), Point::new(selection.start.row + 1, 0));
3152                selection.goal = SelectionGoal::None;
3153            }
3154
3155            let selection_start = snapshot.point_to_offset(selection.start);
3156
3157            (
3158                snapshot.clone(),
3159                selection.clone(),
3160                editor.display_map.update(cx, |display_map, cx| {
3161                    display_map
3162                        .snapshot(cx)
3163                        .crease_snapshot
3164                        .creases_in_range(
3165                            MultiBufferRow(selection.start.row)
3166                                ..MultiBufferRow(selection.end.row + 1),
3167                            &snapshot,
3168                        )
3169                        .filter_map(|crease| {
3170                            if let Some(metadata) = &crease.metadata {
3171                                let start = crease
3172                                    .range
3173                                    .start
3174                                    .to_offset(&snapshot)
3175                                    .saturating_sub(selection_start);
3176                                let end = crease
3177                                    .range
3178                                    .end
3179                                    .to_offset(&snapshot)
3180                                    .saturating_sub(selection_start);
3181
3182                                let range_relative_to_selection = start..end;
3183
3184                                if range_relative_to_selection.is_empty() {
3185                                    None
3186                                } else {
3187                                    Some(SelectedCreaseMetadata {
3188                                        range_relative_to_selection,
3189                                        crease: metadata.clone(),
3190                                    })
3191                                }
3192                            } else {
3193                                None
3194                            }
3195                        })
3196                        .collect::<Vec<_>>()
3197                }),
3198            )
3199        });
3200
3201        let selection = selection.map(|point| snapshot.point_to_offset(point));
3202        let context = self.context.read(cx);
3203
3204        let mut text = String::new();
3205        for message in context.messages(cx) {
3206            if message.offset_range.start >= selection.range().end {
3207                break;
3208            } else if message.offset_range.end >= selection.range().start {
3209                let range = cmp::max(message.offset_range.start, selection.range().start)
3210                    ..cmp::min(message.offset_range.end, selection.range().end);
3211                if !range.is_empty() {
3212                    for chunk in context.buffer().read(cx).text_for_range(range) {
3213                        text.push_str(chunk);
3214                    }
3215                    if message.offset_range.end < selection.range().end {
3216                        text.push('\n');
3217                    }
3218                }
3219            }
3220        }
3221
3222        (text, CopyMetadata { creases }, vec![selection])
3223    }
3224
3225    fn paste(&mut self, action: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
3226        cx.stop_propagation();
3227
3228        let images = if let Some(item) = cx.read_from_clipboard() {
3229            item.into_entries()
3230                .filter_map(|entry| {
3231                    if let ClipboardEntry::Image(image) = entry {
3232                        Some(image)
3233                    } else {
3234                        None
3235                    }
3236                })
3237                .collect()
3238        } else {
3239            Vec::new()
3240        };
3241
3242        let metadata = if let Some(item) = cx.read_from_clipboard() {
3243            item.entries().first().and_then(|entry| {
3244                if let ClipboardEntry::String(text) = entry {
3245                    text.metadata_json::<CopyMetadata>()
3246                } else {
3247                    None
3248                }
3249            })
3250        } else {
3251            None
3252        };
3253
3254        if images.is_empty() {
3255            self.editor.update(cx, |editor, cx| {
3256                let paste_position = editor.selections.newest::<usize>(cx).head();
3257                editor.paste(action, cx);
3258
3259                if let Some(metadata) = metadata {
3260                    let buffer = editor.buffer().read(cx).snapshot(cx);
3261
3262                    let mut buffer_rows_to_fold = BTreeSet::new();
3263                    let weak_editor = cx.view().downgrade();
3264                    editor.insert_creases(
3265                        metadata.creases.into_iter().map(|metadata| {
3266                            let start = buffer.anchor_after(
3267                                paste_position + metadata.range_relative_to_selection.start,
3268                            );
3269                            let end = buffer.anchor_before(
3270                                paste_position + metadata.range_relative_to_selection.end,
3271                            );
3272
3273                            let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
3274                            buffer_rows_to_fold.insert(buffer_row);
3275                            Crease::new(
3276                                start..end,
3277                                FoldPlaceholder {
3278                                    constrain_width: false,
3279                                    render: render_fold_icon_button(
3280                                        weak_editor.clone(),
3281                                        metadata.crease.icon,
3282                                        metadata.crease.label.clone(),
3283                                    ),
3284                                    merge_adjacent: false,
3285                                },
3286                                render_slash_command_output_toggle,
3287                                |_, _, _| Empty.into_any(),
3288                            )
3289                            .with_metadata(metadata.crease.clone())
3290                        }),
3291                        cx,
3292                    );
3293                    for buffer_row in buffer_rows_to_fold.into_iter().rev() {
3294                        editor.fold_at(&FoldAt { buffer_row }, cx);
3295                    }
3296                }
3297            });
3298        } else {
3299            let mut image_positions = Vec::new();
3300            self.editor.update(cx, |editor, cx| {
3301                editor.transact(cx, |editor, cx| {
3302                    let edits = editor
3303                        .selections
3304                        .all::<usize>(cx)
3305                        .into_iter()
3306                        .map(|selection| (selection.start..selection.end, "\n"));
3307                    editor.edit(edits, cx);
3308
3309                    let snapshot = editor.buffer().read(cx).snapshot(cx);
3310                    for selection in editor.selections.all::<usize>(cx) {
3311                        image_positions.push(snapshot.anchor_before(selection.end));
3312                    }
3313                });
3314            });
3315
3316            self.context.update(cx, |context, cx| {
3317                for image in images {
3318                    let Some(render_image) = image.to_image_data(cx).log_err() else {
3319                        continue;
3320                    };
3321                    let image_id = image.id();
3322                    let image_task = LanguageModelImage::from_image(image, cx).shared();
3323
3324                    for image_position in image_positions.iter() {
3325                        context.insert_content(
3326                            Content::Image {
3327                                anchor: image_position.text_anchor,
3328                                image_id,
3329                                image: image_task.clone(),
3330                                render_image: render_image.clone(),
3331                            },
3332                            cx,
3333                        );
3334                    }
3335                }
3336            });
3337        }
3338    }
3339
3340    fn update_image_blocks(&mut self, cx: &mut ViewContext<Self>) {
3341        self.editor.update(cx, |editor, cx| {
3342            let buffer = editor.buffer().read(cx).snapshot(cx);
3343            let excerpt_id = *buffer.as_singleton().unwrap().0;
3344            let old_blocks = std::mem::take(&mut self.image_blocks);
3345            let new_blocks = self
3346                .context
3347                .read(cx)
3348                .contents(cx)
3349                .filter_map(|content| {
3350                    if let Content::Image {
3351                        anchor,
3352                        render_image,
3353                        ..
3354                    } = content
3355                    {
3356                        Some((anchor, render_image))
3357                    } else {
3358                        None
3359                    }
3360                })
3361                .filter_map(|(anchor, render_image)| {
3362                    const MAX_HEIGHT_IN_LINES: u32 = 8;
3363                    let anchor = buffer.anchor_in_excerpt(excerpt_id, anchor).unwrap();
3364                    let image = render_image.clone();
3365                    anchor.is_valid(&buffer).then(|| BlockProperties {
3366                        placement: BlockPlacement::Above(anchor),
3367                        height: MAX_HEIGHT_IN_LINES,
3368                        style: BlockStyle::Sticky,
3369                        render: Box::new(move |cx| {
3370                            let image_size = size_for_image(
3371                                &image,
3372                                size(
3373                                    cx.max_width - cx.gutter_dimensions.full_width(),
3374                                    MAX_HEIGHT_IN_LINES as f32 * cx.line_height,
3375                                ),
3376                            );
3377                            h_flex()
3378                                .pl(cx.gutter_dimensions.full_width())
3379                                .child(
3380                                    img(image.clone())
3381                                        .object_fit(gpui::ObjectFit::ScaleDown)
3382                                        .w(image_size.width)
3383                                        .h(image_size.height),
3384                                )
3385                                .into_any_element()
3386                        }),
3387                        priority: 0,
3388                    })
3389                })
3390                .collect::<Vec<_>>();
3391
3392            editor.remove_blocks(old_blocks, None, cx);
3393            let ids = editor.insert_blocks(new_blocks, None, cx);
3394            self.image_blocks = HashSet::from_iter(ids);
3395        });
3396    }
3397
3398    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
3399        self.context.update(cx, |context, cx| {
3400            let selections = self.editor.read(cx).selections.disjoint_anchors();
3401            for selection in selections.as_ref() {
3402                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
3403                let range = selection
3404                    .map(|endpoint| endpoint.to_offset(&buffer))
3405                    .range();
3406                context.split_message(range, cx);
3407            }
3408        });
3409    }
3410
3411    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
3412        self.context.update(cx, |context, cx| {
3413            context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
3414        });
3415    }
3416
3417    fn title(&self, cx: &AppContext) -> Cow<str> {
3418        self.context
3419            .read(cx)
3420            .summary()
3421            .map(|summary| summary.text.clone())
3422            .map(Cow::Owned)
3423            .unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
3424    }
3425
3426    fn render_patch_header(
3427        &self,
3428        range: Range<text::Anchor>,
3429        _id: FoldId,
3430        cx: &mut ViewContext<Self>,
3431    ) -> Option<AnyElement> {
3432        let patch = self.context.read(cx).patch_for_range(&range, cx)?;
3433        let theme = cx.theme().clone();
3434        Some(
3435            h_flex()
3436                .px_1()
3437                .py_0p5()
3438                .border_b_1()
3439                .border_color(theme.status().info_border)
3440                .gap_1()
3441                .child(Icon::new(IconName::Diff).size(IconSize::Small))
3442                .child(Label::new(patch.title.clone()).size(LabelSize::Small))
3443                .into_any(),
3444        )
3445    }
3446
3447    fn render_patch_footer(
3448        &mut self,
3449        range: Range<text::Anchor>,
3450        max_width: Pixels,
3451        gutter_width: Pixels,
3452        id: BlockId,
3453        cx: &mut ViewContext<Self>,
3454    ) -> Option<AnyElement> {
3455        let snapshot = self.editor.update(cx, |editor, cx| editor.snapshot(cx));
3456        let (excerpt_id, _buffer_id, _) = snapshot.buffer_snapshot.as_singleton().unwrap();
3457        let excerpt_id = *excerpt_id;
3458        let anchor = snapshot
3459            .buffer_snapshot
3460            .anchor_in_excerpt(excerpt_id, range.start)
3461            .unwrap();
3462
3463        if !snapshot.intersects_fold(anchor) {
3464            return None;
3465        }
3466
3467        let patch = self.context.read(cx).patch_for_range(&range, cx)?;
3468        let paths = patch
3469            .paths()
3470            .map(|p| SharedString::from(p.to_string()))
3471            .collect::<BTreeSet<_>>();
3472
3473        Some(
3474            v_flex()
3475                .id(id)
3476                .pl(gutter_width)
3477                .w(max_width)
3478                .py_2()
3479                .cursor(CursorStyle::PointingHand)
3480                .on_click(cx.listener(move |this, _, cx| {
3481                    this.editor.update(cx, |editor, cx| {
3482                        editor.change_selections(None, cx, |selections| {
3483                            selections.select_ranges(vec![anchor..anchor]);
3484                        });
3485                    });
3486                    this.focus_active_patch(cx);
3487                }))
3488                .children(paths.into_iter().map(|path| {
3489                    h_flex()
3490                        .pl_1()
3491                        .gap_1()
3492                        .child(Icon::new(IconName::File).size(IconSize::Small))
3493                        .child(Label::new(path).size(LabelSize::Small))
3494                }))
3495                .when(patch.status == AssistantPatchStatus::Pending, |div| {
3496                    div.child(
3497                        Label::new("Generating")
3498                            .color(Color::Muted)
3499                            .size(LabelSize::Small)
3500                            .with_animation(
3501                                "pulsating-label",
3502                                Animation::new(Duration::from_secs(2))
3503                                    .repeat()
3504                                    .with_easing(pulsating_between(0.4, 1.)),
3505                                |label, delta| label.alpha(delta),
3506                            ),
3507                    )
3508                })
3509                .into_any(),
3510        )
3511    }
3512
3513    fn render_notice(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
3514        use feature_flags::FeatureFlagAppExt;
3515        let nudge = self.assistant_panel.upgrade().map(|assistant_panel| {
3516            assistant_panel.read(cx).show_zed_ai_notice && cx.has_flag::<feature_flags::ZedPro>()
3517        });
3518
3519        if nudge.map_or(false, |value| value) {
3520            Some(
3521                h_flex()
3522                    .p_3()
3523                    .border_b_1()
3524                    .border_color(cx.theme().colors().border_variant)
3525                    .bg(cx.theme().colors().editor_background)
3526                    .justify_between()
3527                    .child(
3528                        h_flex()
3529                            .gap_3()
3530                            .child(Icon::new(IconName::ZedAssistant).color(Color::Accent))
3531                            .child(Label::new("Zed AI is here! Get started by signing in →")),
3532                    )
3533                    .child(
3534                        Button::new("sign-in", "Sign in")
3535                            .size(ButtonSize::Compact)
3536                            .style(ButtonStyle::Filled)
3537                            .on_click(cx.listener(|this, _event, cx| {
3538                                let client = this
3539                                    .workspace
3540                                    .update(cx, |workspace, _| workspace.client().clone())
3541                                    .log_err();
3542
3543                                if let Some(client) = client {
3544                                    cx.spawn(|this, mut cx| async move {
3545                                        client.authenticate_and_connect(true, &mut cx).await?;
3546                                        this.update(&mut cx, |_, cx| cx.notify())
3547                                    })
3548                                    .detach_and_log_err(cx)
3549                                }
3550                            })),
3551                    )
3552                    .into_any_element(),
3553            )
3554        } else if let Some(configuration_error) = configuration_error(cx) {
3555            let label = match configuration_error {
3556                ConfigurationError::NoProvider => "No LLM provider selected.",
3557                ConfigurationError::ProviderNotAuthenticated => "LLM provider is not configured.",
3558            };
3559            Some(
3560                h_flex()
3561                    .px_3()
3562                    .py_2()
3563                    .border_b_1()
3564                    .border_color(cx.theme().colors().border_variant)
3565                    .bg(cx.theme().colors().editor_background)
3566                    .justify_between()
3567                    .child(
3568                        h_flex()
3569                            .gap_3()
3570                            .child(
3571                                Icon::new(IconName::Warning)
3572                                    .size(IconSize::Small)
3573                                    .color(Color::Warning),
3574                            )
3575                            .child(Label::new(label)),
3576                    )
3577                    .child(
3578                        Button::new("open-configuration", "Configure Providers")
3579                            .size(ButtonSize::Compact)
3580                            .icon(Some(IconName::SlidersVertical))
3581                            .icon_size(IconSize::Small)
3582                            .icon_position(IconPosition::Start)
3583                            .style(ButtonStyle::Filled)
3584                            .on_click({
3585                                let focus_handle = self.focus_handle(cx).clone();
3586                                move |_event, cx| {
3587                                    focus_handle.dispatch_action(&ShowConfiguration, cx);
3588                                }
3589                            }),
3590                    )
3591                    .into_any_element(),
3592            )
3593        } else {
3594            None
3595        }
3596    }
3597
3598    fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3599        let focus_handle = self.focus_handle(cx).clone();
3600
3601        let (style, tooltip) = match token_state(&self.context, cx) {
3602            Some(TokenState::NoTokensLeft { .. }) => (
3603                ButtonStyle::Tinted(TintColor::Negative),
3604                Some(Tooltip::text("Token limit reached", cx)),
3605            ),
3606            Some(TokenState::HasMoreTokens {
3607                over_warn_threshold,
3608                ..
3609            }) => {
3610                let (style, tooltip) = if over_warn_threshold {
3611                    (
3612                        ButtonStyle::Tinted(TintColor::Warning),
3613                        Some(Tooltip::text("Token limit is close to exhaustion", cx)),
3614                    )
3615                } else {
3616                    (ButtonStyle::Filled, None)
3617                };
3618                (style, tooltip)
3619            }
3620            None => (ButtonStyle::Filled, None),
3621        };
3622
3623        let provider = LanguageModelRegistry::read_global(cx).active_provider();
3624
3625        let has_configuration_error = configuration_error(cx).is_some();
3626        let needs_to_accept_terms = self.show_accept_terms
3627            && provider
3628                .as_ref()
3629                .map_or(false, |provider| provider.must_accept_terms(cx));
3630        let disabled = has_configuration_error || needs_to_accept_terms;
3631
3632        ButtonLike::new("send_button")
3633            .disabled(disabled)
3634            .style(style)
3635            .when_some(tooltip, |button, tooltip| {
3636                button.tooltip(move |_| tooltip.clone())
3637            })
3638            .layer(ElevationIndex::ModalSurface)
3639            .child(Label::new(
3640                if AssistantSettings::get_global(cx).are_live_diffs_enabled(cx) {
3641                    "Chat"
3642                } else {
3643                    "Send"
3644                },
3645            ))
3646            .children(
3647                KeyBinding::for_action_in(&Assist, &focus_handle, cx)
3648                    .map(|binding| binding.into_any_element()),
3649            )
3650            .on_click(move |_event, cx| {
3651                focus_handle.dispatch_action(&Assist, cx);
3652            })
3653    }
3654
3655    fn render_edit_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3656        let focus_handle = self.focus_handle(cx).clone();
3657
3658        let (style, tooltip) = match token_state(&self.context, cx) {
3659            Some(TokenState::NoTokensLeft { .. }) => (
3660                ButtonStyle::Tinted(TintColor::Negative),
3661                Some(Tooltip::text("Token limit reached", cx)),
3662            ),
3663            Some(TokenState::HasMoreTokens {
3664                over_warn_threshold,
3665                ..
3666            }) => {
3667                let (style, tooltip) = if over_warn_threshold {
3668                    (
3669                        ButtonStyle::Tinted(TintColor::Warning),
3670                        Some(Tooltip::text("Token limit is close to exhaustion", cx)),
3671                    )
3672                } else {
3673                    (ButtonStyle::Filled, None)
3674                };
3675                (style, tooltip)
3676            }
3677            None => (ButtonStyle::Filled, None),
3678        };
3679
3680        let provider = LanguageModelRegistry::read_global(cx).active_provider();
3681
3682        let has_configuration_error = configuration_error(cx).is_some();
3683        let needs_to_accept_terms = self.show_accept_terms
3684            && provider
3685                .as_ref()
3686                .map_or(false, |provider| provider.must_accept_terms(cx));
3687        let disabled = has_configuration_error || needs_to_accept_terms;
3688
3689        ButtonLike::new("edit_button")
3690            .disabled(disabled)
3691            .style(style)
3692            .when_some(tooltip, |button, tooltip| {
3693                button.tooltip(move |_| tooltip.clone())
3694            })
3695            .layer(ElevationIndex::ModalSurface)
3696            .child(Label::new("Suggest Edits"))
3697            .children(
3698                KeyBinding::for_action_in(&Edit, &focus_handle, cx)
3699                    .map(|binding| binding.into_any_element()),
3700            )
3701            .on_click(move |_event, cx| {
3702                focus_handle.dispatch_action(&Edit, cx);
3703            })
3704    }
3705
3706    fn render_last_error(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
3707        let last_error = self.last_error.as_ref()?;
3708
3709        Some(
3710            div()
3711                .absolute()
3712                .right_3()
3713                .bottom_12()
3714                .max_w_96()
3715                .py_2()
3716                .px_3()
3717                .elevation_2(cx)
3718                .occlude()
3719                .child(match last_error {
3720                    AssistError::FileRequired => self.render_file_required_error(cx),
3721                    AssistError::PaymentRequired => self.render_payment_required_error(cx),
3722                    AssistError::MaxMonthlySpendReached => {
3723                        self.render_max_monthly_spend_reached_error(cx)
3724                    }
3725                    AssistError::Message(error_message) => {
3726                        self.render_assist_error(error_message, cx)
3727                    }
3728                })
3729                .into_any(),
3730        )
3731    }
3732
3733    fn render_file_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
3734        v_flex()
3735            .gap_0p5()
3736            .child(
3737                h_flex()
3738                    .gap_1p5()
3739                    .items_center()
3740                    .child(Icon::new(IconName::Warning).color(Color::Warning))
3741                    .child(
3742                        Label::new("Suggest Edits needs a file to edit").weight(FontWeight::MEDIUM),
3743                    ),
3744            )
3745            .child(
3746                div()
3747                    .id("error-message")
3748                    .max_h_24()
3749                    .overflow_y_scroll()
3750                    .child(Label::new(
3751                        "To include files, type /file or /tab in your prompt.",
3752                    )),
3753            )
3754            .child(
3755                h_flex()
3756                    .justify_end()
3757                    .mt_1()
3758                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
3759                        |this, _, cx| {
3760                            this.last_error = None;
3761                            cx.notify();
3762                        },
3763                    ))),
3764            )
3765            .into_any()
3766    }
3767
3768    fn render_payment_required_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
3769        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.";
3770
3771        v_flex()
3772            .gap_0p5()
3773            .child(
3774                h_flex()
3775                    .gap_1p5()
3776                    .items_center()
3777                    .child(Icon::new(IconName::XCircle).color(Color::Error))
3778                    .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
3779            )
3780            .child(
3781                div()
3782                    .id("error-message")
3783                    .max_h_24()
3784                    .overflow_y_scroll()
3785                    .child(Label::new(ERROR_MESSAGE)),
3786            )
3787            .child(
3788                h_flex()
3789                    .justify_end()
3790                    .mt_1()
3791                    .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
3792                        |this, _, cx| {
3793                            this.last_error = None;
3794                            cx.open_url(&zed_urls::account_url(cx));
3795                            cx.notify();
3796                        },
3797                    )))
3798                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
3799                        |this, _, cx| {
3800                            this.last_error = None;
3801                            cx.notify();
3802                        },
3803                    ))),
3804            )
3805            .into_any()
3806    }
3807
3808    fn render_max_monthly_spend_reached_error(&self, cx: &mut ViewContext<Self>) -> AnyElement {
3809        const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
3810
3811        v_flex()
3812            .gap_0p5()
3813            .child(
3814                h_flex()
3815                    .gap_1p5()
3816                    .items_center()
3817                    .child(Icon::new(IconName::XCircle).color(Color::Error))
3818                    .child(Label::new("Max Monthly Spend Reached").weight(FontWeight::MEDIUM)),
3819            )
3820            .child(
3821                div()
3822                    .id("error-message")
3823                    .max_h_24()
3824                    .overflow_y_scroll()
3825                    .child(Label::new(ERROR_MESSAGE)),
3826            )
3827            .child(
3828                h_flex()
3829                    .justify_end()
3830                    .mt_1()
3831                    .child(
3832                        Button::new("subscribe", "Update Monthly Spend Limit").on_click(
3833                            cx.listener(|this, _, cx| {
3834                                this.last_error = None;
3835                                cx.open_url(&zed_urls::account_url(cx));
3836                                cx.notify();
3837                            }),
3838                        ),
3839                    )
3840                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
3841                        |this, _, cx| {
3842                            this.last_error = None;
3843                            cx.notify();
3844                        },
3845                    ))),
3846            )
3847            .into_any()
3848    }
3849
3850    fn render_assist_error(
3851        &self,
3852        error_message: &SharedString,
3853        cx: &mut ViewContext<Self>,
3854    ) -> AnyElement {
3855        v_flex()
3856            .gap_0p5()
3857            .child(
3858                h_flex()
3859                    .gap_1p5()
3860                    .items_center()
3861                    .child(Icon::new(IconName::XCircle).color(Color::Error))
3862                    .child(
3863                        Label::new("Error interacting with language model")
3864                            .weight(FontWeight::MEDIUM),
3865                    ),
3866            )
3867            .child(
3868                div()
3869                    .id("error-message")
3870                    .max_h_24()
3871                    .overflow_y_scroll()
3872                    .child(Label::new(error_message.clone())),
3873            )
3874            .child(
3875                h_flex()
3876                    .justify_end()
3877                    .mt_1()
3878                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
3879                        |this, _, cx| {
3880                            this.last_error = None;
3881                            cx.notify();
3882                        },
3883                    ))),
3884            )
3885            .into_any()
3886    }
3887}
3888
3889/// Returns the contents of the *outermost* fenced code block that contains the given offset.
3890fn find_surrounding_code_block(snapshot: &BufferSnapshot, offset: usize) -> Option<Range<usize>> {
3891    const CODE_BLOCK_NODE: &'static str = "fenced_code_block";
3892    const CODE_BLOCK_CONTENT: &'static str = "code_fence_content";
3893
3894    let layer = snapshot.syntax_layers().next()?;
3895
3896    let root_node = layer.node();
3897    let mut cursor = root_node.walk();
3898
3899    // Go to the first child for the given offset
3900    while cursor.goto_first_child_for_byte(offset).is_some() {
3901        // If we're at the end of the node, go to the next one.
3902        // Example: if you have a fenced-code-block, and you're on the start of the line
3903        // right after the closing ```, you want to skip the fenced-code-block and
3904        // go to the next sibling.
3905        if cursor.node().end_byte() == offset {
3906            cursor.goto_next_sibling();
3907        }
3908
3909        if cursor.node().start_byte() > offset {
3910            break;
3911        }
3912
3913        // We found the fenced code block.
3914        if cursor.node().kind() == CODE_BLOCK_NODE {
3915            // Now we need to find the child node that contains the code.
3916            cursor.goto_first_child();
3917            loop {
3918                if cursor.node().kind() == CODE_BLOCK_CONTENT {
3919                    return Some(cursor.node().byte_range());
3920                }
3921                if !cursor.goto_next_sibling() {
3922                    break;
3923                }
3924            }
3925        }
3926    }
3927
3928    None
3929}
3930
3931pub fn selections_creases(
3932    workspace: &mut workspace::Workspace,
3933    cx: &mut ViewContext<Workspace>,
3934) -> Option<Vec<(String, String)>> {
3935    let editor = workspace
3936        .active_item(cx)
3937        .and_then(|item| item.act_as::<Editor>(cx))?;
3938
3939    let mut creases = vec![];
3940    editor.update(cx, |editor, cx| {
3941        let selections = editor.selections.all_adjusted(cx);
3942        let buffer = editor.buffer().read(cx).snapshot(cx);
3943        for selection in selections {
3944            let range = editor::ToOffset::to_offset(&selection.start, &buffer)
3945                ..editor::ToOffset::to_offset(&selection.end, &buffer);
3946            let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
3947            if selected_text.is_empty() {
3948                continue;
3949            }
3950            let start_language = buffer.language_at(range.start);
3951            let end_language = buffer.language_at(range.end);
3952            let language_name = if start_language == end_language {
3953                start_language.map(|language| language.code_fence_block_name())
3954            } else {
3955                None
3956            };
3957            let language_name = language_name.as_deref().unwrap_or("");
3958            let filename = buffer
3959                .file_at(selection.start)
3960                .map(|file| file.full_path(cx));
3961            let text = if language_name == "markdown" {
3962                selected_text
3963                    .lines()
3964                    .map(|line| format!("> {}", line))
3965                    .collect::<Vec<_>>()
3966                    .join("\n")
3967            } else {
3968                let start_symbols = buffer
3969                    .symbols_containing(selection.start, None)
3970                    .map(|(_, symbols)| symbols);
3971                let end_symbols = buffer
3972                    .symbols_containing(selection.end, None)
3973                    .map(|(_, symbols)| symbols);
3974
3975                let outline_text =
3976                    if let Some((start_symbols, end_symbols)) = start_symbols.zip(end_symbols) {
3977                        Some(
3978                            start_symbols
3979                                .into_iter()
3980                                .zip(end_symbols)
3981                                .take_while(|(a, b)| a == b)
3982                                .map(|(a, _)| a.text)
3983                                .collect::<Vec<_>>()
3984                                .join(" > "),
3985                        )
3986                    } else {
3987                        None
3988                    };
3989
3990                let line_comment_prefix = start_language
3991                    .and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
3992
3993                let fence = codeblock_fence_for_path(
3994                    filename.as_deref(),
3995                    Some(selection.start.row..=selection.end.row),
3996                );
3997
3998                if let Some((line_comment_prefix, outline_text)) =
3999                    line_comment_prefix.zip(outline_text)
4000                {
4001                    let breadcrumb = format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
4002                    format!("{fence}{breadcrumb}{selected_text}\n```")
4003                } else {
4004                    format!("{fence}{selected_text}\n```")
4005                }
4006            };
4007            let crease_title = if let Some(path) = filename {
4008                let start_line = selection.start.row + 1;
4009                let end_line = selection.end.row + 1;
4010                if start_line == end_line {
4011                    format!("{}, Line {}", path.display(), start_line)
4012                } else {
4013                    format!("{}, Lines {} to {}", path.display(), start_line, end_line)
4014                }
4015            } else {
4016                "Quoted selection".to_string()
4017            };
4018            creases.push((text, crease_title));
4019        }
4020    });
4021    Some(creases)
4022}
4023
4024fn render_fold_icon_button(
4025    editor: WeakView<Editor>,
4026    icon: IconName,
4027    label: SharedString,
4028) -> Arc<dyn Send + Sync + Fn(FoldId, Range<Anchor>, &mut WindowContext) -> AnyElement> {
4029    Arc::new(move |fold_id, fold_range, _cx| {
4030        let editor = editor.clone();
4031        ButtonLike::new(fold_id)
4032            .style(ButtonStyle::Filled)
4033            .layer(ElevationIndex::ElevatedSurface)
4034            .child(Icon::new(icon))
4035            .child(Label::new(label.clone()).single_line())
4036            .on_click(move |_, cx| {
4037                editor
4038                    .update(cx, |editor, cx| {
4039                        let buffer_start = fold_range
4040                            .start
4041                            .to_point(&editor.buffer().read(cx).read(cx));
4042                        let buffer_row = MultiBufferRow(buffer_start.row);
4043                        editor.unfold_at(&UnfoldAt { buffer_row }, cx);
4044                    })
4045                    .ok();
4046            })
4047            .into_any_element()
4048    })
4049}
4050
4051#[derive(Debug, Clone, Serialize, Deserialize)]
4052struct CopyMetadata {
4053    creases: Vec<SelectedCreaseMetadata>,
4054}
4055
4056#[derive(Debug, Clone, Serialize, Deserialize)]
4057struct SelectedCreaseMetadata {
4058    range_relative_to_selection: Range<usize>,
4059    crease: CreaseMetadata,
4060}
4061
4062impl EventEmitter<EditorEvent> for ContextEditor {}
4063impl EventEmitter<SearchEvent> for ContextEditor {}
4064
4065impl Render for ContextEditor {
4066    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4067        let provider = LanguageModelRegistry::read_global(cx).active_provider();
4068        let accept_terms = if self.show_accept_terms {
4069            provider
4070                .as_ref()
4071                .and_then(|provider| provider.render_accept_terms(cx))
4072        } else {
4073            None
4074        };
4075
4076        v_flex()
4077            .key_context("ContextEditor")
4078            .capture_action(cx.listener(ContextEditor::cancel))
4079            .capture_action(cx.listener(ContextEditor::save))
4080            .capture_action(cx.listener(ContextEditor::copy))
4081            .capture_action(cx.listener(ContextEditor::cut))
4082            .capture_action(cx.listener(ContextEditor::paste))
4083            .capture_action(cx.listener(ContextEditor::cycle_message_role))
4084            .capture_action(cx.listener(ContextEditor::confirm_command))
4085            .on_action(cx.listener(ContextEditor::edit))
4086            .on_action(cx.listener(ContextEditor::assist))
4087            .on_action(cx.listener(ContextEditor::split))
4088            .size_full()
4089            .children(self.render_notice(cx))
4090            .child(
4091                div()
4092                    .flex_grow()
4093                    .bg(cx.theme().colors().editor_background)
4094                    .child(self.editor.clone()),
4095            )
4096            .when_some(accept_terms, |this, element| {
4097                this.child(
4098                    div()
4099                        .absolute()
4100                        .right_3()
4101                        .bottom_12()
4102                        .max_w_96()
4103                        .py_2()
4104                        .px_3()
4105                        .elevation_2(cx)
4106                        .bg(cx.theme().colors().surface_background)
4107                        .occlude()
4108                        .child(element),
4109                )
4110            })
4111            .children(self.render_last_error(cx))
4112            .child(
4113                h_flex().w_full().relative().child(
4114                    h_flex()
4115                        .p_2()
4116                        .w_full()
4117                        .border_t_1()
4118                        .border_color(cx.theme().colors().border_variant)
4119                        .bg(cx.theme().colors().editor_background)
4120                        .child(
4121                            h_flex()
4122                                .gap_1()
4123                                .child(render_inject_context_menu(cx.view().downgrade(), cx)),
4124                        )
4125                        .child(
4126                            h_flex()
4127                                .w_full()
4128                                .justify_end()
4129                                .when(
4130                                    AssistantSettings::get_global(cx).are_live_diffs_enabled(cx),
4131                                    |buttons| {
4132                                        buttons
4133                                            .items_center()
4134                                            .gap_1p5()
4135                                            .child(self.render_edit_button(cx))
4136                                            .child(
4137                                                Label::new("or")
4138                                                    .size(LabelSize::Small)
4139                                                    .color(Color::Muted),
4140                                            )
4141                                    },
4142                                )
4143                                .child(self.render_send_button(cx)),
4144                        ),
4145                ),
4146            )
4147    }
4148}
4149
4150impl FocusableView for ContextEditor {
4151    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4152        self.editor.focus_handle(cx)
4153    }
4154}
4155
4156impl Item for ContextEditor {
4157    type Event = editor::EditorEvent;
4158
4159    fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
4160        Some(util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into())
4161    }
4162
4163    fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
4164        match event {
4165            EditorEvent::Edited { .. } => {
4166                f(item::ItemEvent::Edit);
4167            }
4168            EditorEvent::TitleChanged => {
4169                f(item::ItemEvent::UpdateTab);
4170            }
4171            _ => {}
4172        }
4173    }
4174
4175    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
4176        Some(self.title(cx).to_string().into())
4177    }
4178
4179    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
4180        Some(Box::new(handle.clone()))
4181    }
4182
4183    fn set_nav_history(&mut self, nav_history: pane::ItemNavHistory, cx: &mut ViewContext<Self>) {
4184        self.editor.update(cx, |editor, cx| {
4185            Item::set_nav_history(editor, nav_history, cx)
4186        })
4187    }
4188
4189    fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
4190        self.editor
4191            .update(cx, |editor, cx| Item::navigate(editor, data, cx))
4192    }
4193
4194    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
4195        self.editor.update(cx, Item::deactivated)
4196    }
4197
4198    fn act_as_type<'a>(
4199        &'a self,
4200        type_id: TypeId,
4201        self_handle: &'a View<Self>,
4202        _: &'a AppContext,
4203    ) -> Option<AnyView> {
4204        if type_id == TypeId::of::<Self>() {
4205            Some(self_handle.to_any())
4206        } else if type_id == TypeId::of::<Editor>() {
4207            Some(self.editor.to_any())
4208        } else {
4209            None
4210        }
4211    }
4212}
4213
4214impl SearchableItem for ContextEditor {
4215    type Match = <Editor as SearchableItem>::Match;
4216
4217    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
4218        self.editor.update(cx, |editor, cx| {
4219            editor.clear_matches(cx);
4220        });
4221    }
4222
4223    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
4224        self.editor
4225            .update(cx, |editor, cx| editor.update_matches(matches, cx));
4226    }
4227
4228    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
4229        self.editor
4230            .update(cx, |editor, cx| editor.query_suggestion(cx))
4231    }
4232
4233    fn activate_match(
4234        &mut self,
4235        index: usize,
4236        matches: &[Self::Match],
4237        cx: &mut ViewContext<Self>,
4238    ) {
4239        self.editor.update(cx, |editor, cx| {
4240            editor.activate_match(index, matches, cx);
4241        });
4242    }
4243
4244    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
4245        self.editor
4246            .update(cx, |editor, cx| editor.select_matches(matches, cx));
4247    }
4248
4249    fn replace(
4250        &mut self,
4251        identifier: &Self::Match,
4252        query: &project::search::SearchQuery,
4253        cx: &mut ViewContext<Self>,
4254    ) {
4255        self.editor
4256            .update(cx, |editor, cx| editor.replace(identifier, query, cx));
4257    }
4258
4259    fn find_matches(
4260        &mut self,
4261        query: Arc<project::search::SearchQuery>,
4262        cx: &mut ViewContext<Self>,
4263    ) -> Task<Vec<Self::Match>> {
4264        self.editor
4265            .update(cx, |editor, cx| editor.find_matches(query, cx))
4266    }
4267
4268    fn active_match_index(
4269        &mut self,
4270        matches: &[Self::Match],
4271        cx: &mut ViewContext<Self>,
4272    ) -> Option<usize> {
4273        self.editor
4274            .update(cx, |editor, cx| editor.active_match_index(matches, cx))
4275    }
4276}
4277
4278impl FollowableItem for ContextEditor {
4279    fn remote_id(&self) -> Option<workspace::ViewId> {
4280        self.remote_id
4281    }
4282
4283    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
4284        let context = self.context.read(cx);
4285        Some(proto::view::Variant::ContextEditor(
4286            proto::view::ContextEditor {
4287                context_id: context.id().to_proto(),
4288                editor: if let Some(proto::view::Variant::Editor(proto)) =
4289                    self.editor.read(cx).to_state_proto(cx)
4290                {
4291                    Some(proto)
4292                } else {
4293                    None
4294                },
4295            },
4296        ))
4297    }
4298
4299    fn from_state_proto(
4300        workspace: View<Workspace>,
4301        id: workspace::ViewId,
4302        state: &mut Option<proto::view::Variant>,
4303        cx: &mut WindowContext,
4304    ) -> Option<Task<Result<View<Self>>>> {
4305        let proto::view::Variant::ContextEditor(_) = state.as_ref()? else {
4306            return None;
4307        };
4308        let Some(proto::view::Variant::ContextEditor(state)) = state.take() else {
4309            unreachable!()
4310        };
4311
4312        let context_id = ContextId::from_proto(state.context_id);
4313        let editor_state = state.editor?;
4314
4315        let (project, panel) = workspace.update(cx, |workspace, cx| {
4316            Some((
4317                workspace.project().clone(),
4318                workspace.panel::<AssistantPanel>(cx)?,
4319            ))
4320        })?;
4321
4322        let context_editor =
4323            panel.update(cx, |panel, cx| panel.open_remote_context(context_id, cx));
4324
4325        Some(cx.spawn(|mut cx| async move {
4326            let context_editor = context_editor.await?;
4327            context_editor
4328                .update(&mut cx, |context_editor, cx| {
4329                    context_editor.remote_id = Some(id);
4330                    context_editor.editor.update(cx, |editor, cx| {
4331                        editor.apply_update_proto(
4332                            &project,
4333                            proto::update_view::Variant::Editor(proto::update_view::Editor {
4334                                selections: editor_state.selections,
4335                                pending_selection: editor_state.pending_selection,
4336                                scroll_top_anchor: editor_state.scroll_top_anchor,
4337                                scroll_x: editor_state.scroll_y,
4338                                scroll_y: editor_state.scroll_y,
4339                                ..Default::default()
4340                            }),
4341                            cx,
4342                        )
4343                    })
4344                })?
4345                .await?;
4346            Ok(context_editor)
4347        }))
4348    }
4349
4350    fn to_follow_event(event: &Self::Event) -> Option<item::FollowEvent> {
4351        Editor::to_follow_event(event)
4352    }
4353
4354    fn add_event_to_update_proto(
4355        &self,
4356        event: &Self::Event,
4357        update: &mut Option<proto::update_view::Variant>,
4358        cx: &WindowContext,
4359    ) -> bool {
4360        self.editor
4361            .read(cx)
4362            .add_event_to_update_proto(event, update, cx)
4363    }
4364
4365    fn apply_update_proto(
4366        &mut self,
4367        project: &Model<Project>,
4368        message: proto::update_view::Variant,
4369        cx: &mut ViewContext<Self>,
4370    ) -> Task<Result<()>> {
4371        self.editor.update(cx, |editor, cx| {
4372            editor.apply_update_proto(project, message, cx)
4373        })
4374    }
4375
4376    fn is_project_item(&self, _cx: &WindowContext) -> bool {
4377        true
4378    }
4379
4380    fn set_leader_peer_id(
4381        &mut self,
4382        leader_peer_id: Option<proto::PeerId>,
4383        cx: &mut ViewContext<Self>,
4384    ) {
4385        self.editor.update(cx, |editor, cx| {
4386            editor.set_leader_peer_id(leader_peer_id, cx)
4387        })
4388    }
4389
4390    fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<item::Dedup> {
4391        if existing.context.read(cx).id() == self.context.read(cx).id() {
4392            Some(item::Dedup::KeepExisting)
4393        } else {
4394            None
4395        }
4396    }
4397}
4398
4399pub struct ContextEditorToolbarItem {
4400    fs: Arc<dyn Fs>,
4401    active_context_editor: Option<WeakView<ContextEditor>>,
4402    model_summary_editor: View<Editor>,
4403    model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
4404}
4405
4406fn render_inject_context_menu(
4407    active_context_editor: WeakView<ContextEditor>,
4408    cx: &mut WindowContext<'_>,
4409) -> impl IntoElement {
4410    let commands = SlashCommandRegistry::global(cx);
4411
4412    slash_command_picker::SlashCommandSelector::new(
4413        commands.clone(),
4414        active_context_editor,
4415        Button::new("trigger", "Add Context")
4416            .icon(IconName::Plus)
4417            .icon_size(IconSize::Small)
4418            .icon_color(Color::Muted)
4419            .icon_position(IconPosition::Start)
4420            .tooltip(|cx| Tooltip::text("Type / to insert via keyboard", cx)),
4421    )
4422}
4423
4424impl ContextEditorToolbarItem {
4425    pub fn new(
4426        workspace: &Workspace,
4427        model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
4428        model_summary_editor: View<Editor>,
4429    ) -> Self {
4430        Self {
4431            fs: workspace.app_state().fs.clone(),
4432            active_context_editor: None,
4433            model_summary_editor,
4434            model_selector_menu_handle,
4435        }
4436    }
4437
4438    fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
4439        let context = &self
4440            .active_context_editor
4441            .as_ref()?
4442            .upgrade()?
4443            .read(cx)
4444            .context;
4445        let (token_count_color, token_count, max_token_count) = match token_state(context, cx)? {
4446            TokenState::NoTokensLeft {
4447                max_token_count,
4448                token_count,
4449            } => (Color::Error, token_count, max_token_count),
4450            TokenState::HasMoreTokens {
4451                max_token_count,
4452                token_count,
4453                over_warn_threshold,
4454            } => {
4455                let color = if over_warn_threshold {
4456                    Color::Warning
4457                } else {
4458                    Color::Muted
4459                };
4460                (color, token_count, max_token_count)
4461            }
4462        };
4463        Some(
4464            h_flex()
4465                .gap_0p5()
4466                .child(
4467                    Label::new(humanize_token_count(token_count))
4468                        .size(LabelSize::Small)
4469                        .color(token_count_color),
4470                )
4471                .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
4472                .child(
4473                    Label::new(humanize_token_count(max_token_count))
4474                        .size(LabelSize::Small)
4475                        .color(Color::Muted),
4476                ),
4477        )
4478    }
4479}
4480
4481impl Render for ContextEditorToolbarItem {
4482    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4483        let left_side = h_flex()
4484            .group("chat-title-group")
4485            .pl_0p5()
4486            .gap_1()
4487            .items_center()
4488            .flex_grow()
4489            .child(
4490                div()
4491                    .w_full()
4492                    .when(self.active_context_editor.is_some(), |left_side| {
4493                        left_side.child(self.model_summary_editor.clone())
4494                    }),
4495            )
4496            .child(
4497                div().visible_on_hover("chat-title-group").child(
4498                    IconButton::new("regenerate-context", IconName::RefreshTitle)
4499                        .shape(ui::IconButtonShape::Square)
4500                        .tooltip(|cx| Tooltip::text("Regenerate Title", cx))
4501                        .on_click(cx.listener(move |_, _, cx| {
4502                            cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
4503                        })),
4504                ),
4505            );
4506        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
4507        let active_model = LanguageModelRegistry::read_global(cx).active_model();
4508        let right_side = h_flex()
4509            .gap_2()
4510            // TODO display this in a nicer way, once we have a design for it.
4511            // .children({
4512            //     let project = self
4513            //         .workspace
4514            //         .upgrade()
4515            //         .map(|workspace| workspace.read(cx).project().downgrade());
4516            //
4517            //     let scan_items_remaining = cx.update_global(|db: &mut SemanticDb, cx| {
4518            //         project.and_then(|project| db.remaining_summaries(&project, cx))
4519            //     });
4520            //     scan_items_remaining
4521            //         .map(|remaining_items| format!("Files to scan: {}", remaining_items))
4522            // })
4523            .child(
4524                ModelSelector::new(
4525                    self.fs.clone(),
4526                    ButtonLike::new("active-model")
4527                        .style(ButtonStyle::Subtle)
4528                        .child(
4529                            h_flex()
4530                                .w_full()
4531                                .gap_0p5()
4532                                .child(
4533                                    div()
4534                                        .overflow_x_hidden()
4535                                        .flex_grow()
4536                                        .whitespace_nowrap()
4537                                        .child(match (active_provider, active_model) {
4538                                            (Some(provider), Some(model)) => h_flex()
4539                                                .gap_1()
4540                                                .child(
4541                                                    Icon::new(
4542                                                        model
4543                                                            .icon()
4544                                                            .unwrap_or_else(|| provider.icon()),
4545                                                    )
4546                                                    .color(Color::Muted)
4547                                                    .size(IconSize::XSmall),
4548                                                )
4549                                                .child(
4550                                                    Label::new(model.name().0)
4551                                                        .size(LabelSize::Small)
4552                                                        .color(Color::Muted),
4553                                                )
4554                                                .into_any_element(),
4555                                            _ => Label::new("No model selected")
4556                                                .size(LabelSize::Small)
4557                                                .color(Color::Muted)
4558                                                .into_any_element(),
4559                                        }),
4560                                )
4561                                .child(
4562                                    Icon::new(IconName::ChevronDown)
4563                                        .color(Color::Muted)
4564                                        .size(IconSize::XSmall),
4565                                ),
4566                        )
4567                        .tooltip(move |cx| {
4568                            Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
4569                        }),
4570                )
4571                .with_handle(self.model_selector_menu_handle.clone()),
4572            )
4573            .children(self.render_remaining_tokens(cx));
4574
4575        h_flex()
4576            .size_full()
4577            .gap_2()
4578            .justify_between()
4579            .child(left_side)
4580            .child(right_side)
4581    }
4582}
4583
4584impl ToolbarItemView for ContextEditorToolbarItem {
4585    fn set_active_pane_item(
4586        &mut self,
4587        active_pane_item: Option<&dyn ItemHandle>,
4588        cx: &mut ViewContext<Self>,
4589    ) -> ToolbarItemLocation {
4590        self.active_context_editor = active_pane_item
4591            .and_then(|item| item.act_as::<ContextEditor>(cx))
4592            .map(|editor| editor.downgrade());
4593        cx.notify();
4594        if self.active_context_editor.is_none() {
4595            ToolbarItemLocation::Hidden
4596        } else {
4597            ToolbarItemLocation::PrimaryRight
4598        }
4599    }
4600
4601    fn pane_focus_update(&mut self, _pane_focused: bool, cx: &mut ViewContext<Self>) {
4602        cx.notify();
4603    }
4604}
4605
4606impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
4607
4608enum ContextEditorToolbarItemEvent {
4609    RegenerateSummary,
4610}
4611impl EventEmitter<ContextEditorToolbarItemEvent> for ContextEditorToolbarItem {}
4612
4613pub struct ContextHistory {
4614    picker: View<Picker<SavedContextPickerDelegate>>,
4615    _subscriptions: Vec<Subscription>,
4616    assistant_panel: WeakView<AssistantPanel>,
4617}
4618
4619impl ContextHistory {
4620    fn new(
4621        project: Model<Project>,
4622        context_store: Model<ContextStore>,
4623        assistant_panel: WeakView<AssistantPanel>,
4624        cx: &mut ViewContext<Self>,
4625    ) -> Self {
4626        let picker = cx.new_view(|cx| {
4627            Picker::uniform_list(
4628                SavedContextPickerDelegate::new(project, context_store.clone()),
4629                cx,
4630            )
4631            .modal(false)
4632            .max_height(None)
4633        });
4634
4635        let _subscriptions = vec![
4636            cx.observe(&context_store, |this, _, cx| {
4637                this.picker.update(cx, |picker, cx| picker.refresh(cx));
4638            }),
4639            cx.subscribe(&picker, Self::handle_picker_event),
4640        ];
4641
4642        Self {
4643            picker,
4644            _subscriptions,
4645            assistant_panel,
4646        }
4647    }
4648
4649    fn handle_picker_event(
4650        &mut self,
4651        _: View<Picker<SavedContextPickerDelegate>>,
4652        event: &SavedContextPickerEvent,
4653        cx: &mut ViewContext<Self>,
4654    ) {
4655        let SavedContextPickerEvent::Confirmed(context) = event;
4656        self.assistant_panel
4657            .update(cx, |assistant_panel, cx| match context {
4658                ContextMetadata::Remote(metadata) => {
4659                    assistant_panel
4660                        .open_remote_context(metadata.id.clone(), cx)
4661                        .detach_and_log_err(cx);
4662                }
4663                ContextMetadata::Saved(metadata) => {
4664                    assistant_panel
4665                        .open_saved_context(metadata.path.clone(), cx)
4666                        .detach_and_log_err(cx);
4667                }
4668            })
4669            .ok();
4670    }
4671}
4672
4673#[derive(Debug, PartialEq, Eq, Clone, Copy)]
4674pub enum WorkflowAssistStatus {
4675    Pending,
4676    Confirmed,
4677    Done,
4678    Idle,
4679}
4680
4681impl Render for ContextHistory {
4682    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
4683        div().size_full().child(self.picker.clone())
4684    }
4685}
4686
4687impl FocusableView for ContextHistory {
4688    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4689        self.picker.focus_handle(cx)
4690    }
4691}
4692
4693impl EventEmitter<()> for ContextHistory {}
4694
4695impl Item for ContextHistory {
4696    type Event = ();
4697
4698    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
4699        Some("History".into())
4700    }
4701}
4702
4703pub struct ConfigurationView {
4704    focus_handle: FocusHandle,
4705    configuration_views: HashMap<LanguageModelProviderId, AnyView>,
4706    _registry_subscription: Subscription,
4707}
4708
4709impl ConfigurationView {
4710    fn new(cx: &mut ViewContext<Self>) -> Self {
4711        let focus_handle = cx.focus_handle();
4712
4713        let registry_subscription = cx.subscribe(
4714            &LanguageModelRegistry::global(cx),
4715            |this, _, event: &language_model::Event, cx| match event {
4716                language_model::Event::AddedProvider(provider_id) => {
4717                    let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
4718                    if let Some(provider) = provider {
4719                        this.add_configuration_view(&provider, cx);
4720                    }
4721                }
4722                language_model::Event::RemovedProvider(provider_id) => {
4723                    this.remove_configuration_view(provider_id);
4724                }
4725                _ => {}
4726            },
4727        );
4728
4729        let mut this = Self {
4730            focus_handle,
4731            configuration_views: HashMap::default(),
4732            _registry_subscription: registry_subscription,
4733        };
4734        this.build_configuration_views(cx);
4735        this
4736    }
4737
4738    fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
4739        let providers = LanguageModelRegistry::read_global(cx).providers();
4740        for provider in providers {
4741            self.add_configuration_view(&provider, cx);
4742        }
4743    }
4744
4745    fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
4746        self.configuration_views.remove(provider_id);
4747    }
4748
4749    fn add_configuration_view(
4750        &mut self,
4751        provider: &Arc<dyn LanguageModelProvider>,
4752        cx: &mut ViewContext<Self>,
4753    ) {
4754        let configuration_view = provider.configuration_view(cx);
4755        self.configuration_views
4756            .insert(provider.id(), configuration_view);
4757    }
4758
4759    fn render_provider_view(
4760        &mut self,
4761        provider: &Arc<dyn LanguageModelProvider>,
4762        cx: &mut ViewContext<Self>,
4763    ) -> Div {
4764        let provider_id = provider.id().0.clone();
4765        let provider_name = provider.name().0.clone();
4766        let configuration_view = self.configuration_views.get(&provider.id()).cloned();
4767
4768        let open_new_context = cx.listener({
4769            let provider = provider.clone();
4770            move |_, _, cx| {
4771                cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
4772                    provider.clone(),
4773                ))
4774            }
4775        });
4776
4777        v_flex()
4778            .gap_2()
4779            .child(
4780                h_flex()
4781                    .justify_between()
4782                    .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
4783                    .when(provider.is_authenticated(cx), move |this| {
4784                        this.child(
4785                            h_flex().justify_end().child(
4786                                Button::new(
4787                                    SharedString::from(format!("new-context-{provider_id}")),
4788                                    "Open New Chat",
4789                                )
4790                                .icon_position(IconPosition::Start)
4791                                .icon(IconName::Plus)
4792                                .style(ButtonStyle::Filled)
4793                                .layer(ElevationIndex::ModalSurface)
4794                                .on_click(open_new_context),
4795                            ),
4796                        )
4797                    }),
4798            )
4799            .child(
4800                div()
4801                    .p(Spacing::Large.rems(cx))
4802                    .bg(cx.theme().colors().surface_background)
4803                    .border_1()
4804                    .border_color(cx.theme().colors().border_variant)
4805                    .rounded_md()
4806                    .when(configuration_view.is_none(), |this| {
4807                        this.child(div().child(Label::new(format!(
4808                            "No configuration view for {}",
4809                            provider_name
4810                        ))))
4811                    })
4812                    .when_some(configuration_view, |this, configuration_view| {
4813                        this.child(configuration_view)
4814                    }),
4815            )
4816    }
4817}
4818
4819impl Render for ConfigurationView {
4820    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4821        let providers = LanguageModelRegistry::read_global(cx).providers();
4822        let provider_views = providers
4823            .into_iter()
4824            .map(|provider| self.render_provider_view(&provider, cx))
4825            .collect::<Vec<_>>();
4826
4827        let mut element = v_flex()
4828            .id("assistant-configuration-view")
4829            .track_focus(&self.focus_handle(cx))
4830            .bg(cx.theme().colors().editor_background)
4831            .size_full()
4832            .overflow_y_scroll()
4833            .child(
4834                v_flex()
4835                    .p(Spacing::XXLarge.rems(cx))
4836                    .border_b_1()
4837                    .border_color(cx.theme().colors().border)
4838                    .gap_1()
4839                    .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
4840                    .child(
4841                        Label::new(
4842                            "At least one LLM provider must be configured to use the Assistant.",
4843                        )
4844                        .color(Color::Muted),
4845                    ),
4846            )
4847            .child(
4848                v_flex()
4849                    .p(Spacing::XXLarge.rems(cx))
4850                    .mt_1()
4851                    .gap_6()
4852                    .flex_1()
4853                    .children(provider_views),
4854            )
4855            .into_any();
4856
4857        // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
4858        // because we couldn't the element to take up the size of the parent.
4859        canvas(
4860            move |bounds, cx| {
4861                element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
4862                element
4863            },
4864            |_, mut element, cx| {
4865                element.paint(cx);
4866            },
4867        )
4868        .flex_1()
4869        .w_full()
4870    }
4871}
4872
4873pub enum ConfigurationViewEvent {
4874    NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
4875}
4876
4877impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
4878
4879impl FocusableView for ConfigurationView {
4880    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
4881        self.focus_handle.clone()
4882    }
4883}
4884
4885impl Item for ConfigurationView {
4886    type Event = ConfigurationViewEvent;
4887
4888    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
4889        Some("Configuration".into())
4890    }
4891}
4892
4893type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
4894
4895fn render_slash_command_output_toggle(
4896    row: MultiBufferRow,
4897    is_folded: bool,
4898    fold: ToggleFold,
4899    _cx: &mut WindowContext,
4900) -> AnyElement {
4901    Disclosure::new(
4902        ("slash-command-output-fold-indicator", row.0 as u64),
4903        !is_folded,
4904    )
4905    .selected(is_folded)
4906    .on_click(move |_e, cx| fold(!is_folded, cx))
4907    .into_any_element()
4908}
4909
4910fn fold_toggle(
4911    name: &'static str,
4912) -> impl Fn(
4913    MultiBufferRow,
4914    bool,
4915    Arc<dyn Fn(bool, &mut WindowContext<'_>) + Send + Sync>,
4916    &mut WindowContext<'_>,
4917) -> AnyElement {
4918    move |row, is_folded, fold, _cx| {
4919        Disclosure::new((name, row.0 as u64), !is_folded)
4920            .selected(is_folded)
4921            .on_click(move |_e, cx| fold(!is_folded, cx))
4922            .into_any_element()
4923    }
4924}
4925
4926fn quote_selection_fold_placeholder(title: String, editor: WeakView<Editor>) -> FoldPlaceholder {
4927    FoldPlaceholder {
4928        render: Arc::new({
4929            move |fold_id, fold_range, _cx| {
4930                let editor = editor.clone();
4931                ButtonLike::new(fold_id)
4932                    .style(ButtonStyle::Filled)
4933                    .layer(ElevationIndex::ElevatedSurface)
4934                    .child(Icon::new(IconName::TextSnippet))
4935                    .child(Label::new(title.clone()).single_line())
4936                    .on_click(move |_, cx| {
4937                        editor
4938                            .update(cx, |editor, cx| {
4939                                let buffer_start = fold_range
4940                                    .start
4941                                    .to_point(&editor.buffer().read(cx).read(cx));
4942                                let buffer_row = MultiBufferRow(buffer_start.row);
4943                                editor.unfold_at(&UnfoldAt { buffer_row }, cx);
4944                            })
4945                            .ok();
4946                    })
4947                    .into_any_element()
4948            }
4949        }),
4950        constrain_width: false,
4951        merge_adjacent: false,
4952    }
4953}
4954
4955fn render_quote_selection_output_toggle(
4956    row: MultiBufferRow,
4957    is_folded: bool,
4958    fold: ToggleFold,
4959    _cx: &mut WindowContext,
4960) -> AnyElement {
4961    Disclosure::new(("quote-selection-indicator", row.0 as u64), !is_folded)
4962        .selected(is_folded)
4963        .on_click(move |_e, cx| fold(!is_folded, cx))
4964        .into_any_element()
4965}
4966
4967fn render_pending_slash_command_gutter_decoration(
4968    row: MultiBufferRow,
4969    status: &PendingSlashCommandStatus,
4970    confirm_command: Arc<dyn Fn(&mut WindowContext)>,
4971) -> AnyElement {
4972    let mut icon = IconButton::new(
4973        ("slash-command-gutter-decoration", row.0),
4974        ui::IconName::TriangleRight,
4975    )
4976    .on_click(move |_e, cx| confirm_command(cx))
4977    .icon_size(ui::IconSize::Small)
4978    .size(ui::ButtonSize::None);
4979
4980    match status {
4981        PendingSlashCommandStatus::Idle => {
4982            icon = icon.icon_color(Color::Muted);
4983        }
4984        PendingSlashCommandStatus::Running { .. } => {
4985            icon = icon.selected(true);
4986        }
4987        PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
4988    }
4989
4990    icon.into_any_element()
4991}
4992
4993fn render_docs_slash_command_trailer(
4994    row: MultiBufferRow,
4995    command: PendingSlashCommand,
4996    cx: &mut WindowContext,
4997) -> AnyElement {
4998    if command.arguments.is_empty() {
4999        return Empty.into_any();
5000    }
5001    let args = DocsSlashCommandArgs::parse(&command.arguments);
5002
5003    let Some(store) = args
5004        .provider()
5005        .and_then(|provider| IndexedDocsStore::try_global(provider, cx).ok())
5006    else {
5007        return Empty.into_any();
5008    };
5009
5010    let Some(package) = args.package() else {
5011        return Empty.into_any();
5012    };
5013
5014    let mut children = Vec::new();
5015
5016    if store.is_indexing(&package) {
5017        children.push(
5018            div()
5019                .id(("crates-being-indexed", row.0))
5020                .child(Icon::new(IconName::ArrowCircle).with_animation(
5021                    "arrow-circle",
5022                    Animation::new(Duration::from_secs(4)).repeat(),
5023                    |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
5024                ))
5025                .tooltip({
5026                    let package = package.clone();
5027                    move |cx| Tooltip::text(format!("Indexing {package}"), cx)
5028                })
5029                .into_any_element(),
5030        );
5031    }
5032
5033    if let Some(latest_error) = store.latest_error_for_package(&package) {
5034        children.push(
5035            div()
5036                .id(("latest-error", row.0))
5037                .child(
5038                    Icon::new(IconName::Warning)
5039                        .size(IconSize::Small)
5040                        .color(Color::Warning),
5041                )
5042                .tooltip(move |cx| Tooltip::text(format!("Failed to index: {latest_error}"), cx))
5043                .into_any_element(),
5044        )
5045    }
5046
5047    let is_indexing = store.is_indexing(&package);
5048    let latest_error = store.latest_error_for_package(&package);
5049
5050    if !is_indexing && latest_error.is_none() {
5051        return Empty.into_any();
5052    }
5053
5054    h_flex().gap_2().children(children).into_any_element()
5055}
5056
5057fn make_lsp_adapter_delegate(
5058    project: &Model<Project>,
5059    cx: &mut AppContext,
5060) -> Result<Option<Arc<dyn LspAdapterDelegate>>> {
5061    project.update(cx, |project, cx| {
5062        // TODO: Find the right worktree.
5063        let Some(worktree) = project.worktrees(cx).next() else {
5064            return Ok(None::<Arc<dyn LspAdapterDelegate>>);
5065        };
5066        let http_client = project.client().http_client().clone();
5067        project.lsp_store().update(cx, |lsp_store, cx| {
5068            Ok(Some(LocalLspAdapterDelegate::new(
5069                lsp_store,
5070                &worktree,
5071                http_client,
5072                project.fs().clone(),
5073                cx,
5074            ) as Arc<dyn LspAdapterDelegate>))
5075        })
5076    })
5077}
5078
5079fn slash_command_error_block_renderer(message: String) -> RenderBlock {
5080    Box::new(move |_| {
5081        div()
5082            .pl_6()
5083            .child(
5084                Label::new(format!("error: {}", message))
5085                    .single_line()
5086                    .color(Color::Error),
5087            )
5088            .into_any()
5089    })
5090}
5091
5092enum TokenState {
5093    NoTokensLeft {
5094        max_token_count: usize,
5095        token_count: usize,
5096    },
5097    HasMoreTokens {
5098        max_token_count: usize,
5099        token_count: usize,
5100        over_warn_threshold: bool,
5101    },
5102}
5103
5104fn token_state(context: &Model<Context>, cx: &AppContext) -> Option<TokenState> {
5105    const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
5106
5107    let model = LanguageModelRegistry::read_global(cx).active_model()?;
5108    let token_count = context.read(cx).token_count()?;
5109    let max_token_count = model.max_token_count();
5110
5111    let remaining_tokens = max_token_count as isize - token_count as isize;
5112    let token_state = if remaining_tokens <= 0 {
5113        TokenState::NoTokensLeft {
5114            max_token_count,
5115            token_count,
5116        }
5117    } else {
5118        let over_warn_threshold =
5119            token_count as f32 / max_token_count as f32 >= WARNING_TOKEN_THRESHOLD;
5120        TokenState::HasMoreTokens {
5121            max_token_count,
5122            token_count,
5123            over_warn_threshold,
5124        }
5125    };
5126    Some(token_state)
5127}
5128
5129fn size_for_image(data: &RenderImage, max_size: Size<Pixels>) -> Size<Pixels> {
5130    let image_size = data
5131        .size(0)
5132        .map(|dimension| Pixels::from(u32::from(dimension)));
5133    let image_ratio = image_size.width / image_size.height;
5134    let bounds_ratio = max_size.width / max_size.height;
5135
5136    if image_size.width > max_size.width || image_size.height > max_size.height {
5137        if bounds_ratio > image_ratio {
5138            size(
5139                image_size.width * (max_size.height / image_size.height),
5140                max_size.height,
5141            )
5142        } else {
5143            size(
5144                max_size.width,
5145                image_size.height * (max_size.width / image_size.width),
5146            )
5147        }
5148    } else {
5149        size(image_size.width, image_size.height)
5150    }
5151}
5152
5153enum ConfigurationError {
5154    NoProvider,
5155    ProviderNotAuthenticated,
5156}
5157
5158fn configuration_error(cx: &AppContext) -> Option<ConfigurationError> {
5159    let provider = LanguageModelRegistry::read_global(cx).active_provider();
5160    let is_authenticated = provider
5161        .as_ref()
5162        .map_or(false, |provider| provider.is_authenticated(cx));
5163
5164    if provider.is_some() && is_authenticated {
5165        return None;
5166    }
5167
5168    if provider.is_none() {
5169        return Some(ConfigurationError::NoProvider);
5170    }
5171
5172    if !is_authenticated {
5173        return Some(ConfigurationError::ProviderNotAuthenticated);
5174    }
5175
5176    None
5177}
5178
5179#[cfg(test)]
5180mod tests {
5181    use super::*;
5182    use gpui::{AppContext, Context};
5183    use language::Buffer;
5184    use unindent::Unindent;
5185
5186    #[gpui::test]
5187    fn test_find_code_blocks(cx: &mut AppContext) {
5188        let markdown = languages::language("markdown", tree_sitter_md::LANGUAGE.into());
5189
5190        let buffer = cx.new_model(|cx| {
5191            let text = r#"
5192                line 0
5193                line 1
5194                ```rust
5195                fn main() {}
5196                ```
5197                line 5
5198                line 6
5199                line 7
5200                ```go
5201                func main() {}
5202                ```
5203                line 11
5204                ```
5205                this is plain text code block
5206                ```
5207
5208                ```go
5209                func another() {}
5210                ```
5211                line 19
5212            "#
5213            .unindent();
5214            let mut buffer = Buffer::local(text, cx);
5215            buffer.set_language(Some(markdown.clone()), cx);
5216            buffer
5217        });
5218        let snapshot = buffer.read(cx).snapshot();
5219
5220        let code_blocks = vec![
5221            Point::new(3, 0)..Point::new(4, 0),
5222            Point::new(9, 0)..Point::new(10, 0),
5223            Point::new(13, 0)..Point::new(14, 0),
5224            Point::new(17, 0)..Point::new(18, 0),
5225        ]
5226        .into_iter()
5227        .map(|range| snapshot.point_to_offset(range.start)..snapshot.point_to_offset(range.end))
5228        .collect::<Vec<_>>();
5229
5230        let expected_results = vec![
5231            (0, None),
5232            (1, None),
5233            (2, Some(code_blocks[0].clone())),
5234            (3, Some(code_blocks[0].clone())),
5235            (4, Some(code_blocks[0].clone())),
5236            (5, None),
5237            (6, None),
5238            (7, None),
5239            (8, Some(code_blocks[1].clone())),
5240            (9, Some(code_blocks[1].clone())),
5241            (10, Some(code_blocks[1].clone())),
5242            (11, None),
5243            (12, Some(code_blocks[2].clone())),
5244            (13, Some(code_blocks[2].clone())),
5245            (14, Some(code_blocks[2].clone())),
5246            (15, None),
5247            (16, Some(code_blocks[3].clone())),
5248            (17, Some(code_blocks[3].clone())),
5249            (18, Some(code_blocks[3].clone())),
5250            (19, None),
5251        ];
5252
5253        for (row, expected) in expected_results {
5254            let offset = snapshot.point_to_offset(Point::new(row, 0));
5255            let range = find_surrounding_code_block(&snapshot, offset);
5256            assert_eq!(range, expected, "unexpected result on row {:?}", row);
5257        }
5258    }
5259}