assistant_panel.rs

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