assistant_panel.rs

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