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