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