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