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