assistant_panel.rs

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