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