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}
1359
1360impl WorkflowStep {
1361    fn status(&self, cx: &AppContext) -> WorkflowStepStatus {
1362        match self.resolved_step.as_ref() {
1363            Some(Ok(step)) => {
1364                if step.suggestion_groups.is_empty() {
1365                    WorkflowStepStatus::Empty
1366                } else if let Some(assist) = self.assist.as_ref() {
1367                    let assistant = InlineAssistant::global(cx);
1368                    if assist
1369                        .assist_ids
1370                        .iter()
1371                        .any(|assist_id| assistant.assist_status(*assist_id, cx).is_pending())
1372                    {
1373                        WorkflowStepStatus::Pending
1374                    } else if assist
1375                        .assist_ids
1376                        .iter()
1377                        .all(|assist_id| assistant.assist_status(*assist_id, cx).is_confirmed())
1378                    {
1379                        WorkflowStepStatus::Confirmed
1380                    } else if assist
1381                        .assist_ids
1382                        .iter()
1383                        .all(|assist_id| assistant.assist_status(*assist_id, cx).is_done())
1384                    {
1385                        WorkflowStepStatus::Done
1386                    } else {
1387                        WorkflowStepStatus::Idle
1388                    }
1389                } else {
1390                    WorkflowStepStatus::Idle
1391                }
1392            }
1393            Some(Err(error)) => WorkflowStepStatus::Error(error.clone()),
1394            None => WorkflowStepStatus::Resolving,
1395        }
1396    }
1397}
1398
1399enum WorkflowStepStatus {
1400    Resolving,
1401    Error(Arc<anyhow::Error>),
1402    Empty,
1403    Idle,
1404    Pending,
1405    Done,
1406    Confirmed,
1407}
1408
1409impl WorkflowStepStatus {
1410    pub(crate) fn is_confirmed(&self) -> bool {
1411        matches!(self, Self::Confirmed)
1412    }
1413
1414    fn render_workflow_step_error(
1415        id: EntityId,
1416        editor: WeakView<ContextEditor>,
1417        step_range: Range<language::Anchor>,
1418        error: String,
1419    ) -> AnyElement {
1420        h_flex()
1421            .gap_2()
1422            .child(
1423                div()
1424                    .id("step-resolution-failure")
1425                    .child(
1426                        Label::new("Step Resolution Failed")
1427                            .size(LabelSize::Small)
1428                            .color(Color::Error),
1429                    )
1430                    .tooltip(move |cx| Tooltip::text(error.clone(), cx)),
1431            )
1432            .child(
1433                Button::new(("transform", id), "Retry")
1434                    .icon(IconName::Update)
1435                    .icon_position(IconPosition::Start)
1436                    .icon_size(IconSize::Small)
1437                    .label_size(LabelSize::Small)
1438                    .on_click({
1439                        let editor = editor.clone();
1440                        let step_range = step_range.clone();
1441                        move |_, cx| {
1442                            editor
1443                                .update(cx, |this, cx| {
1444                                    this.resolve_workflow_step(step_range.clone(), cx)
1445                                })
1446                                .ok();
1447                        }
1448                    }),
1449            )
1450            .into_any()
1451    }
1452
1453    pub(crate) fn into_element(
1454        &self,
1455        step_range: Range<language::Anchor>,
1456        focus_handle: FocusHandle,
1457        editor: WeakView<ContextEditor>,
1458        cx: &mut BlockContext<'_, '_>,
1459    ) -> AnyElement {
1460        let id = EntityId::from(cx.block_id);
1461        fn display_keybind_in_tooltip(
1462            step_range: &Range<language::Anchor>,
1463            editor: &WeakView<ContextEditor>,
1464            cx: &mut WindowContext<'_>,
1465        ) -> bool {
1466            editor
1467                .update(cx, |this, _| {
1468                    this.active_workflow_step
1469                        .as_ref()
1470                        .map(|step| &step.range == step_range)
1471                })
1472                .ok()
1473                .flatten()
1474                .unwrap_or_default()
1475        }
1476        match self {
1477            WorkflowStepStatus::Resolving => Label::new("Resolving")
1478                .size(LabelSize::Small)
1479                .with_animation(
1480                    ("resolving-suggestion-animation", id),
1481                    Animation::new(Duration::from_secs(2))
1482                        .repeat()
1483                        .with_easing(pulsating_between(0.4, 0.8)),
1484                    |label, delta| label.alpha(delta),
1485                )
1486                .into_any_element(),
1487            WorkflowStepStatus::Error(error) => Self::render_workflow_step_error(
1488                id,
1489                editor.clone(),
1490                step_range.clone(),
1491                error.to_string(),
1492            ),
1493            WorkflowStepStatus::Empty => Self::render_workflow_step_error(
1494                id,
1495                editor.clone(),
1496                step_range.clone(),
1497                "Model was unable to locate the code to edit".to_string(),
1498            ),
1499            WorkflowStepStatus::Idle => Button::new(("transform", id), "Transform")
1500                .icon(IconName::SparkleAlt)
1501                .icon_position(IconPosition::Start)
1502                .icon_size(IconSize::Small)
1503                .label_size(LabelSize::Small)
1504                .style(ButtonStyle::Tinted(TintColor::Accent))
1505                .tooltip({
1506                    let step_range = step_range.clone();
1507                    let editor = editor.clone();
1508                    move |cx| {
1509                        cx.new_view(|cx| {
1510                            let tooltip = Tooltip::new("Transform");
1511                            if display_keybind_in_tooltip(&step_range, &editor, cx) {
1512                                tooltip.key_binding(KeyBinding::for_action_in(
1513                                    &Assist,
1514                                    &focus_handle,
1515                                    cx,
1516                                ))
1517                            } else {
1518                                tooltip
1519                            }
1520                        })
1521                        .into()
1522                    }
1523                })
1524                .on_click({
1525                    let editor = editor.clone();
1526                    let step_range = step_range.clone();
1527                    move |_, cx| {
1528                        editor
1529                            .update(cx, |this, cx| {
1530                                this.apply_workflow_step(step_range.clone(), cx)
1531                            })
1532                            .ok();
1533                    }
1534                })
1535                .into_any_element(),
1536            WorkflowStepStatus::Pending => h_flex()
1537                .items_center()
1538                .gap_2()
1539                .child(
1540                    Label::new("Applying...")
1541                        .size(LabelSize::Small)
1542                        .with_animation(
1543                            ("applying-step-transformation-label", id),
1544                            Animation::new(Duration::from_secs(2))
1545                                .repeat()
1546                                .with_easing(pulsating_between(0.4, 0.8)),
1547                            |label, delta| label.alpha(delta),
1548                        ),
1549                )
1550                .child(
1551                    IconButton::new(("stop-transformation", id), IconName::Stop)
1552                        .icon_size(IconSize::Small)
1553                        .icon_color(Color::Error)
1554                        .style(ButtonStyle::Subtle)
1555                        .tooltip({
1556                            let step_range = step_range.clone();
1557                            let editor = editor.clone();
1558                            move |cx| {
1559                                cx.new_view(|cx| {
1560                                    let tooltip = Tooltip::new("Stop Transformation");
1561                                    if display_keybind_in_tooltip(&step_range, &editor, cx) {
1562                                        tooltip.key_binding(KeyBinding::for_action_in(
1563                                            &editor::actions::Cancel,
1564                                            &focus_handle,
1565                                            cx,
1566                                        ))
1567                                    } else {
1568                                        tooltip
1569                                    }
1570                                })
1571                                .into()
1572                            }
1573                        })
1574                        .on_click({
1575                            let editor = editor.clone();
1576                            let step_range = step_range.clone();
1577                            move |_, cx| {
1578                                editor
1579                                    .update(cx, |this, cx| {
1580                                        this.stop_workflow_step(step_range.clone(), cx)
1581                                    })
1582                                    .ok();
1583                            }
1584                        }),
1585                )
1586                .into_any_element(),
1587            WorkflowStepStatus::Done => h_flex()
1588                .gap_1()
1589                .child(
1590                    IconButton::new(("stop-transformation", id), IconName::Close)
1591                        .icon_size(IconSize::Small)
1592                        .style(ButtonStyle::Tinted(TintColor::Negative))
1593                        .tooltip({
1594                            let focus_handle = focus_handle.clone();
1595                            let editor = editor.clone();
1596                            let step_range = step_range.clone();
1597                            move |cx| {
1598                                cx.new_view(|cx| {
1599                                    let tooltip = Tooltip::new("Reject Transformation");
1600                                    if display_keybind_in_tooltip(&step_range, &editor, cx) {
1601                                        tooltip.key_binding(KeyBinding::for_action_in(
1602                                            &editor::actions::Cancel,
1603                                            &focus_handle,
1604                                            cx,
1605                                        ))
1606                                    } else {
1607                                        tooltip
1608                                    }
1609                                })
1610                                .into()
1611                            }
1612                        })
1613                        .on_click({
1614                            let editor = editor.clone();
1615                            let step_range = step_range.clone();
1616                            move |_, cx| {
1617                                editor
1618                                    .update(cx, |this, cx| {
1619                                        this.reject_workflow_step(step_range.clone(), cx);
1620                                    })
1621                                    .ok();
1622                            }
1623                        }),
1624                )
1625                .child(
1626                    Button::new(("confirm-workflow-step", id), "Accept")
1627                        .icon(IconName::Check)
1628                        .icon_position(IconPosition::Start)
1629                        .icon_size(IconSize::Small)
1630                        .label_size(LabelSize::Small)
1631                        .style(ButtonStyle::Tinted(TintColor::Positive))
1632                        .tooltip({
1633                            let editor = editor.clone();
1634                            let step_range = step_range.clone();
1635                            move |cx| {
1636                                cx.new_view(|cx| {
1637                                    let tooltip = Tooltip::new("Accept Transformation");
1638                                    if display_keybind_in_tooltip(&step_range, &editor, cx) {
1639                                        tooltip.key_binding(KeyBinding::for_action_in(
1640                                            &Assist,
1641                                            &focus_handle,
1642                                            cx,
1643                                        ))
1644                                    } else {
1645                                        tooltip
1646                                    }
1647                                })
1648                                .into()
1649                            }
1650                        })
1651                        .on_click({
1652                            let editor = editor.clone();
1653                            let step_range = step_range.clone();
1654                            move |_, cx| {
1655                                editor
1656                                    .update(cx, |this, cx| {
1657                                        this.confirm_workflow_step(step_range.clone(), cx);
1658                                    })
1659                                    .ok();
1660                            }
1661                        }),
1662                )
1663                .into_any_element(),
1664            WorkflowStepStatus::Confirmed => h_flex()
1665                .child(
1666                    Button::new(("revert-workflow-step", id), "Undo")
1667                        .style(ButtonStyle::Filled)
1668                        .icon(Some(IconName::Undo))
1669                        .icon_position(IconPosition::Start)
1670                        .icon_size(IconSize::Small)
1671                        .label_size(LabelSize::Small)
1672                        .on_click({
1673                            let editor = editor.clone();
1674                            let step_range = step_range.clone();
1675                            move |_, cx| {
1676                                editor
1677                                    .update(cx, |this, cx| {
1678                                        this.undo_workflow_step(step_range.clone(), cx);
1679                                    })
1680                                    .ok();
1681                            }
1682                        }),
1683                )
1684                .into_any_element(),
1685        }
1686    }
1687}
1688
1689#[derive(Debug, Eq, PartialEq)]
1690struct ActiveWorkflowStep {
1691    range: Range<language::Anchor>,
1692    resolved: bool,
1693}
1694
1695struct WorkflowAssist {
1696    editor: WeakView<Editor>,
1697    editor_was_open: bool,
1698    assist_ids: Vec<InlineAssistId>,
1699    _observe_assist_status: Task<()>,
1700}
1701
1702pub struct ContextEditor {
1703    context: Model<Context>,
1704    fs: Arc<dyn Fs>,
1705    workspace: WeakView<Workspace>,
1706    project: Model<Project>,
1707    lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1708    editor: View<Editor>,
1709    blocks: HashSet<CustomBlockId>,
1710    image_blocks: HashSet<CustomBlockId>,
1711    scroll_position: Option<ScrollPosition>,
1712    remote_id: Option<workspace::ViewId>,
1713    pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
1714    pending_slash_command_blocks: HashMap<Range<language::Anchor>, CustomBlockId>,
1715    _subscriptions: Vec<Subscription>,
1716    workflow_steps: HashMap<Range<language::Anchor>, WorkflowStep>,
1717    active_workflow_step: Option<ActiveWorkflowStep>,
1718    assistant_panel: WeakView<AssistantPanel>,
1719    error_message: Option<SharedString>,
1720    show_accept_terms: bool,
1721    slash_menu_handle: PopoverMenuHandle<ContextMenu>,
1722}
1723
1724const DEFAULT_TAB_TITLE: &str = "New Context";
1725const MAX_TAB_TITLE_LEN: usize = 16;
1726
1727impl ContextEditor {
1728    fn for_context(
1729        context: Model<Context>,
1730        fs: Arc<dyn Fs>,
1731        workspace: WeakView<Workspace>,
1732        project: Model<Project>,
1733        lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1734        assistant_panel: WeakView<AssistantPanel>,
1735        cx: &mut ViewContext<Self>,
1736    ) -> Self {
1737        let completion_provider = SlashCommandCompletionProvider::new(
1738            Some(cx.view().downgrade()),
1739            Some(workspace.clone()),
1740        );
1741
1742        let editor = cx.new_view(|cx| {
1743            let mut editor = Editor::for_buffer(context.read(cx).buffer().clone(), None, cx);
1744            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
1745            editor.set_show_line_numbers(false, cx);
1746            editor.set_show_git_diff_gutter(false, cx);
1747            editor.set_show_code_actions(false, cx);
1748            editor.set_show_runnables(false, cx);
1749            editor.set_show_wrap_guides(false, cx);
1750            editor.set_show_indent_guides(false, cx);
1751            editor.set_completion_provider(Box::new(completion_provider));
1752            editor.set_collaboration_hub(Box::new(project.clone()));
1753            editor
1754        });
1755
1756        let _subscriptions = vec![
1757            cx.observe(&context, |_, _, cx| cx.notify()),
1758            cx.subscribe(&context, Self::handle_context_event),
1759            cx.subscribe(&editor, Self::handle_editor_event),
1760            cx.subscribe(&editor, Self::handle_editor_search_event),
1761        ];
1762
1763        let sections = context.read(cx).slash_command_output_sections().to_vec();
1764        let mut this = Self {
1765            context,
1766            editor,
1767            lsp_adapter_delegate,
1768            blocks: Default::default(),
1769            image_blocks: Default::default(),
1770            scroll_position: None,
1771            remote_id: None,
1772            fs,
1773            workspace,
1774            project,
1775            pending_slash_command_creases: HashMap::default(),
1776            pending_slash_command_blocks: HashMap::default(),
1777            _subscriptions,
1778            workflow_steps: HashMap::default(),
1779            active_workflow_step: None,
1780            assistant_panel,
1781            error_message: None,
1782            show_accept_terms: false,
1783            slash_menu_handle: Default::default(),
1784        };
1785        this.update_message_headers(cx);
1786        this.update_image_blocks(cx);
1787        this.insert_slash_command_output_sections(sections, cx);
1788        this
1789    }
1790
1791    fn insert_default_prompt(&mut self, cx: &mut ViewContext<Self>) {
1792        let command_name = DefaultSlashCommand.name();
1793        self.editor.update(cx, |editor, cx| {
1794            editor.insert(&format!("/{command_name}"), cx)
1795        });
1796        self.split(&Split, cx);
1797        let command = self.context.update(cx, |context, cx| {
1798            let first_message_id = context.messages(cx).next().unwrap().id;
1799            context.update_metadata(first_message_id, cx, |metadata| {
1800                metadata.role = Role::System;
1801            });
1802            context.reparse_slash_commands(cx);
1803            context.pending_slash_commands()[0].clone()
1804        });
1805
1806        self.run_command(
1807            command.source_range,
1808            &command.name,
1809            &command.arguments,
1810            false,
1811            self.workspace.clone(),
1812            cx,
1813        );
1814    }
1815
1816    fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
1817        let provider = LanguageModelRegistry::read_global(cx).active_provider();
1818        if provider
1819            .as_ref()
1820            .map_or(false, |provider| provider.must_accept_terms(cx))
1821        {
1822            self.show_accept_terms = true;
1823            cx.notify();
1824            return;
1825        }
1826
1827        if !self.apply_active_workflow_step(cx) {
1828            self.error_message = None;
1829            self.send_to_model(cx);
1830            cx.notify();
1831        }
1832    }
1833
1834    fn apply_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1835        self.show_workflow_step(range.clone(), cx);
1836
1837        if let Some(workflow_step) = self.workflow_steps.get(&range) {
1838            if let Some(assist) = workflow_step.assist.as_ref() {
1839                let assist_ids = assist.assist_ids.clone();
1840                cx.window_context().defer(|cx| {
1841                    InlineAssistant::update_global(cx, |assistant, cx| {
1842                        for assist_id in assist_ids {
1843                            assistant.start_assist(assist_id, cx);
1844                        }
1845                    })
1846                });
1847            }
1848        }
1849    }
1850
1851    fn apply_active_workflow_step(&mut self, cx: &mut ViewContext<Self>) -> bool {
1852        let Some(step) = self.active_workflow_step() else {
1853            return false;
1854        };
1855
1856        let range = step.range.clone();
1857        match step.status(cx) {
1858            WorkflowStepStatus::Resolving | WorkflowStepStatus::Pending => true,
1859            WorkflowStepStatus::Idle => {
1860                self.apply_workflow_step(range, cx);
1861                true
1862            }
1863            WorkflowStepStatus::Done => {
1864                self.confirm_workflow_step(range, cx);
1865                true
1866            }
1867            WorkflowStepStatus::Error(_) | WorkflowStepStatus::Empty => {
1868                self.resolve_workflow_step(range, cx);
1869                true
1870            }
1871            WorkflowStepStatus::Confirmed => false,
1872        }
1873    }
1874
1875    fn resolve_workflow_step(
1876        &mut self,
1877        range: Range<language::Anchor>,
1878        cx: &mut ViewContext<Self>,
1879    ) {
1880        self.context
1881            .update(cx, |context, cx| context.resolve_workflow_step(range, cx));
1882    }
1883
1884    fn stop_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1885        if let Some(workflow_step) = self.workflow_steps.get(&range) {
1886            if let Some(assist) = workflow_step.assist.as_ref() {
1887                let assist_ids = assist.assist_ids.clone();
1888                cx.window_context().defer(|cx| {
1889                    InlineAssistant::update_global(cx, |assistant, cx| {
1890                        for assist_id in assist_ids {
1891                            assistant.stop_assist(assist_id, cx);
1892                        }
1893                    })
1894                });
1895            }
1896        }
1897    }
1898
1899    fn undo_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1900        if let Some(workflow_step) = self.workflow_steps.get_mut(&range) {
1901            if let Some(assist) = workflow_step.assist.take() {
1902                cx.window_context().defer(|cx| {
1903                    InlineAssistant::update_global(cx, |assistant, cx| {
1904                        for assist_id in assist.assist_ids {
1905                            assistant.undo_assist(assist_id, cx);
1906                        }
1907                    })
1908                });
1909            }
1910        }
1911    }
1912
1913    fn confirm_workflow_step(
1914        &mut self,
1915        range: Range<language::Anchor>,
1916        cx: &mut ViewContext<Self>,
1917    ) {
1918        if let Some(workflow_step) = self.workflow_steps.get(&range) {
1919            if let Some(assist) = workflow_step.assist.as_ref() {
1920                let assist_ids = assist.assist_ids.clone();
1921                cx.window_context().defer(move |cx| {
1922                    InlineAssistant::update_global(cx, |assistant, cx| {
1923                        for assist_id in assist_ids {
1924                            assistant.finish_assist(assist_id, false, cx);
1925                        }
1926                    })
1927                });
1928            }
1929        }
1930    }
1931
1932    fn reject_workflow_step(&mut self, range: Range<language::Anchor>, cx: &mut ViewContext<Self>) {
1933        if let Some(workflow_step) = self.workflow_steps.get_mut(&range) {
1934            if let Some(assist) = workflow_step.assist.take() {
1935                cx.window_context().defer(move |cx| {
1936                    InlineAssistant::update_global(cx, |assistant, cx| {
1937                        for assist_id in assist.assist_ids {
1938                            assistant.finish_assist(assist_id, true, cx);
1939                        }
1940                    })
1941                });
1942            }
1943        }
1944    }
1945
1946    fn send_to_model(&mut self, cx: &mut ViewContext<Self>) {
1947        if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) {
1948            let new_selection = {
1949                let cursor = user_message
1950                    .start
1951                    .to_offset(self.context.read(cx).buffer().read(cx));
1952                cursor..cursor
1953            };
1954            self.editor.update(cx, |editor, cx| {
1955                editor.change_selections(
1956                    Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
1957                    cx,
1958                    |selections| selections.select_ranges([new_selection]),
1959                );
1960            });
1961            // Avoid scrolling to the new cursor position so the assistant's output is stable.
1962            cx.defer(|this, _| this.scroll_position = None);
1963        }
1964    }
1965
1966    fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
1967        self.error_message = None;
1968
1969        if self
1970            .context
1971            .update(cx, |context, cx| context.cancel_last_assist(cx))
1972        {
1973            return;
1974        }
1975
1976        if let Some(active_step) = self.active_workflow_step() {
1977            match active_step.status(cx) {
1978                WorkflowStepStatus::Pending => {
1979                    self.stop_workflow_step(active_step.range.clone(), cx);
1980                    return;
1981                }
1982                WorkflowStepStatus::Done => {
1983                    self.reject_workflow_step(active_step.range.clone(), cx);
1984                    return;
1985                }
1986                _ => {}
1987            }
1988        }
1989        cx.propagate();
1990    }
1991
1992    fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
1993        let cursors = self.cursors(cx);
1994        self.context.update(cx, |context, cx| {
1995            let messages = context
1996                .messages_for_offsets(cursors, cx)
1997                .into_iter()
1998                .map(|message| message.id)
1999                .collect();
2000            context.cycle_message_roles(messages, cx)
2001        });
2002    }
2003
2004    fn cursors(&self, cx: &AppContext) -> Vec<usize> {
2005        let selections = self.editor.read(cx).selections.all::<usize>(cx);
2006        selections
2007            .into_iter()
2008            .map(|selection| selection.head())
2009            .collect()
2010    }
2011
2012    pub fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
2013        if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
2014            self.editor.update(cx, |editor, cx| {
2015                editor.transact(cx, |editor, cx| {
2016                    editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel());
2017                    let snapshot = editor.buffer().read(cx).snapshot(cx);
2018                    let newest_cursor = editor.selections.newest::<Point>(cx).head();
2019                    if newest_cursor.column > 0
2020                        || snapshot
2021                            .chars_at(newest_cursor)
2022                            .next()
2023                            .map_or(false, |ch| ch != '\n')
2024                    {
2025                        editor.move_to_end_of_line(
2026                            &MoveToEndOfLine {
2027                                stop_at_soft_wraps: false,
2028                            },
2029                            cx,
2030                        );
2031                        editor.newline(&Newline, cx);
2032                    }
2033
2034                    editor.insert(&format!("/{name}"), cx);
2035                    if command.accepts_arguments() {
2036                        editor.insert(" ", cx);
2037                        editor.show_completions(&ShowCompletions::default(), cx);
2038                    }
2039                });
2040            });
2041            if !command.requires_argument() {
2042                self.confirm_command(&ConfirmCommand, cx);
2043            }
2044        }
2045    }
2046
2047    pub fn confirm_command(&mut self, _: &ConfirmCommand, cx: &mut ViewContext<Self>) {
2048        if self.editor.read(cx).has_active_completions_menu() {
2049            return;
2050        }
2051
2052        let selections = self.editor.read(cx).selections.disjoint_anchors();
2053        let mut commands_by_range = HashMap::default();
2054        let workspace = self.workspace.clone();
2055        self.context.update(cx, |context, cx| {
2056            context.reparse_slash_commands(cx);
2057            for selection in selections.iter() {
2058                if let Some(command) =
2059                    context.pending_command_for_position(selection.head().text_anchor, cx)
2060                {
2061                    commands_by_range
2062                        .entry(command.source_range.clone())
2063                        .or_insert_with(|| command.clone());
2064                }
2065            }
2066        });
2067
2068        if commands_by_range.is_empty() {
2069            cx.propagate();
2070        } else {
2071            for command in commands_by_range.into_values() {
2072                self.run_command(
2073                    command.source_range,
2074                    &command.name,
2075                    &command.arguments,
2076                    true,
2077                    workspace.clone(),
2078                    cx,
2079                );
2080            }
2081            cx.stop_propagation();
2082        }
2083    }
2084
2085    pub fn run_command(
2086        &mut self,
2087        command_range: Range<language::Anchor>,
2088        name: &str,
2089        arguments: &[String],
2090        ensure_trailing_newline: bool,
2091        workspace: WeakView<Workspace>,
2092        cx: &mut ViewContext<Self>,
2093    ) {
2094        if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
2095            let output = command.run(arguments, workspace, self.lsp_adapter_delegate.clone(), cx);
2096            self.context.update(cx, |context, cx| {
2097                context.insert_command_output(command_range, output, ensure_trailing_newline, cx)
2098            });
2099        }
2100    }
2101
2102    fn handle_context_event(
2103        &mut self,
2104        _: Model<Context>,
2105        event: &ContextEvent,
2106        cx: &mut ViewContext<Self>,
2107    ) {
2108        let context_editor = cx.view().downgrade();
2109
2110        match event {
2111            ContextEvent::MessagesEdited => {
2112                self.update_message_headers(cx);
2113                self.update_image_blocks(cx);
2114                self.context.update(cx, |context, cx| {
2115                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
2116                });
2117            }
2118            ContextEvent::WorkflowStepsRemoved(removed) => {
2119                self.remove_workflow_steps(removed, cx);
2120                cx.notify();
2121            }
2122            ContextEvent::WorkflowStepUpdated(updated) => {
2123                self.update_workflow_step(updated.clone(), cx);
2124                cx.notify();
2125            }
2126            ContextEvent::SummaryChanged => {
2127                cx.emit(EditorEvent::TitleChanged);
2128                self.context.update(cx, |context, cx| {
2129                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
2130                });
2131            }
2132            ContextEvent::StreamedCompletion => {
2133                self.editor.update(cx, |editor, cx| {
2134                    if let Some(scroll_position) = self.scroll_position {
2135                        let snapshot = editor.snapshot(cx);
2136                        let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
2137                        let scroll_top =
2138                            cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
2139                        editor.set_scroll_position(
2140                            point(scroll_position.offset_before_cursor.x, scroll_top),
2141                            cx,
2142                        );
2143                    }
2144                });
2145            }
2146            ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
2147                self.editor.update(cx, |editor, cx| {
2148                    let buffer = editor.buffer().read(cx).snapshot(cx);
2149                    let (excerpt_id, buffer_id, _) = buffer.as_singleton().unwrap();
2150                    let excerpt_id = *excerpt_id;
2151
2152                    editor.remove_creases(
2153                        removed
2154                            .iter()
2155                            .filter_map(|range| self.pending_slash_command_creases.remove(range)),
2156                        cx,
2157                    );
2158
2159                    editor.remove_blocks(
2160                        HashSet::from_iter(
2161                            removed.iter().filter_map(|range| {
2162                                self.pending_slash_command_blocks.remove(range)
2163                            }),
2164                        ),
2165                        None,
2166                        cx,
2167                    );
2168
2169                    let crease_ids = editor.insert_creases(
2170                        updated.iter().map(|command| {
2171                            let workspace = self.workspace.clone();
2172                            let confirm_command = Arc::new({
2173                                let context_editor = context_editor.clone();
2174                                let command = command.clone();
2175                                move |cx: &mut WindowContext| {
2176                                    context_editor
2177                                        .update(cx, |context_editor, cx| {
2178                                            context_editor.run_command(
2179                                                command.source_range.clone(),
2180                                                &command.name,
2181                                                &command.arguments,
2182                                                false,
2183                                                workspace.clone(),
2184                                                cx,
2185                                            );
2186                                        })
2187                                        .ok();
2188                                }
2189                            });
2190                            let placeholder = FoldPlaceholder {
2191                                render: Arc::new(move |_, _, _| Empty.into_any()),
2192                                constrain_width: false,
2193                                merge_adjacent: false,
2194                            };
2195                            let render_toggle = {
2196                                let confirm_command = confirm_command.clone();
2197                                let command = command.clone();
2198                                move |row, _, _, _cx: &mut WindowContext| {
2199                                    render_pending_slash_command_gutter_decoration(
2200                                        row,
2201                                        &command.status,
2202                                        confirm_command.clone(),
2203                                    )
2204                                }
2205                            };
2206                            let render_trailer = {
2207                                let command = command.clone();
2208                                move |row, _unfold, cx: &mut WindowContext| {
2209                                    // TODO: In the future we should investigate how we can expose
2210                                    // this as a hook on the `SlashCommand` trait so that we don't
2211                                    // need to special-case it here.
2212                                    if command.name == DocsSlashCommand::NAME {
2213                                        return render_docs_slash_command_trailer(
2214                                            row,
2215                                            command.clone(),
2216                                            cx,
2217                                        );
2218                                    }
2219
2220                                    Empty.into_any()
2221                                }
2222                            };
2223
2224                            let start = buffer
2225                                .anchor_in_excerpt(excerpt_id, command.source_range.start)
2226                                .unwrap();
2227                            let end = buffer
2228                                .anchor_in_excerpt(excerpt_id, command.source_range.end)
2229                                .unwrap();
2230                            Crease::new(start..end, placeholder, render_toggle, render_trailer)
2231                        }),
2232                        cx,
2233                    );
2234
2235                    let block_ids = editor.insert_blocks(
2236                        updated
2237                            .iter()
2238                            .filter_map(|command| match &command.status {
2239                                PendingSlashCommandStatus::Error(error) => {
2240                                    Some((command, error.clone()))
2241                                }
2242                                _ => None,
2243                            })
2244                            .map(|(command, error_message)| BlockProperties {
2245                                style: BlockStyle::Fixed,
2246                                position: Anchor {
2247                                    buffer_id: Some(buffer_id),
2248                                    excerpt_id,
2249                                    text_anchor: command.source_range.start,
2250                                },
2251                                height: 1,
2252                                disposition: BlockDisposition::Below,
2253                                render: slash_command_error_block_renderer(error_message),
2254                                priority: 0,
2255                            }),
2256                        None,
2257                        cx,
2258                    );
2259
2260                    self.pending_slash_command_creases.extend(
2261                        updated
2262                            .iter()
2263                            .map(|command| command.source_range.clone())
2264                            .zip(crease_ids),
2265                    );
2266
2267                    self.pending_slash_command_blocks.extend(
2268                        updated
2269                            .iter()
2270                            .map(|command| command.source_range.clone())
2271                            .zip(block_ids),
2272                    );
2273                })
2274            }
2275            ContextEvent::SlashCommandFinished {
2276                output_range,
2277                sections,
2278                run_commands_in_output,
2279            } => {
2280                self.insert_slash_command_output_sections(sections.iter().cloned(), cx);
2281
2282                if *run_commands_in_output {
2283                    let commands = self.context.update(cx, |context, cx| {
2284                        context.reparse_slash_commands(cx);
2285                        context
2286                            .pending_commands_for_range(output_range.clone(), cx)
2287                            .to_vec()
2288                    });
2289
2290                    for command in commands {
2291                        self.run_command(
2292                            command.source_range,
2293                            &command.name,
2294                            &command.arguments,
2295                            false,
2296                            self.workspace.clone(),
2297                            cx,
2298                        );
2299                    }
2300                }
2301            }
2302            ContextEvent::Operation(_) => {}
2303            ContextEvent::ShowAssistError(error_message) => {
2304                self.error_message = Some(error_message.clone());
2305            }
2306        }
2307    }
2308
2309    fn insert_slash_command_output_sections(
2310        &mut self,
2311        sections: impl IntoIterator<Item = SlashCommandOutputSection<language::Anchor>>,
2312        cx: &mut ViewContext<Self>,
2313    ) {
2314        self.editor.update(cx, |editor, cx| {
2315            let buffer = editor.buffer().read(cx).snapshot(cx);
2316            let excerpt_id = *buffer.as_singleton().unwrap().0;
2317            let mut buffer_rows_to_fold = BTreeSet::new();
2318            let mut creases = Vec::new();
2319            for section in sections {
2320                let start = buffer
2321                    .anchor_in_excerpt(excerpt_id, section.range.start)
2322                    .unwrap();
2323                let end = buffer
2324                    .anchor_in_excerpt(excerpt_id, section.range.end)
2325                    .unwrap();
2326                let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2327                buffer_rows_to_fold.insert(buffer_row);
2328                creases.push(Crease::new(
2329                    start..end,
2330                    FoldPlaceholder {
2331                        render: Arc::new({
2332                            let editor = cx.view().downgrade();
2333                            let icon = section.icon;
2334                            let label = section.label.clone();
2335                            move |fold_id, fold_range, _cx| {
2336                                let editor = editor.clone();
2337                                ButtonLike::new(fold_id)
2338                                    .style(ButtonStyle::Filled)
2339                                    .layer(ElevationIndex::ElevatedSurface)
2340                                    .child(Icon::new(icon))
2341                                    .child(Label::new(label.clone()).single_line())
2342                                    .on_click(move |_, cx| {
2343                                        editor
2344                                            .update(cx, |editor, cx| {
2345                                                let buffer_start = fold_range
2346                                                    .start
2347                                                    .to_point(&editor.buffer().read(cx).read(cx));
2348                                                let buffer_row = MultiBufferRow(buffer_start.row);
2349                                                editor.unfold_at(&UnfoldAt { buffer_row }, cx);
2350                                            })
2351                                            .ok();
2352                                    })
2353                                    .into_any_element()
2354                            }
2355                        }),
2356                        constrain_width: false,
2357                        merge_adjacent: false,
2358                    },
2359                    render_slash_command_output_toggle,
2360                    |_, _, _| Empty.into_any_element(),
2361                ));
2362            }
2363
2364            editor.insert_creases(creases, cx);
2365
2366            for buffer_row in buffer_rows_to_fold.into_iter().rev() {
2367                editor.fold_at(&FoldAt { buffer_row }, cx);
2368            }
2369        });
2370    }
2371
2372    fn handle_editor_event(
2373        &mut self,
2374        _: View<Editor>,
2375        event: &EditorEvent,
2376        cx: &mut ViewContext<Self>,
2377    ) {
2378        match event {
2379            EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
2380                let cursor_scroll_position = self.cursor_scroll_position(cx);
2381                if *autoscroll {
2382                    self.scroll_position = cursor_scroll_position;
2383                } else if self.scroll_position != cursor_scroll_position {
2384                    self.scroll_position = None;
2385                }
2386            }
2387            EditorEvent::SelectionsChanged { .. } => {
2388                self.scroll_position = self.cursor_scroll_position(cx);
2389                self.update_active_workflow_step(cx);
2390            }
2391            _ => {}
2392        }
2393        cx.emit(event.clone());
2394    }
2395
2396    fn active_workflow_step(&self) -> Option<&WorkflowStep> {
2397        let step = self.active_workflow_step.as_ref()?;
2398        self.workflow_steps.get(&step.range)
2399    }
2400
2401    fn remove_workflow_steps(
2402        &mut self,
2403        removed_steps: &[Range<language::Anchor>],
2404        cx: &mut ViewContext<Self>,
2405    ) {
2406        let mut blocks_to_remove = HashSet::default();
2407        for step_range in removed_steps {
2408            self.hide_workflow_step(step_range.clone(), cx);
2409            if let Some(step) = self.workflow_steps.remove(step_range) {
2410                blocks_to_remove.insert(step.header_block_id);
2411                blocks_to_remove.insert(step.footer_block_id);
2412            }
2413        }
2414        self.editor.update(cx, |editor, cx| {
2415            editor.remove_blocks(blocks_to_remove, None, cx)
2416        });
2417        self.update_active_workflow_step(cx);
2418    }
2419
2420    fn update_workflow_step(
2421        &mut self,
2422        step_range: Range<language::Anchor>,
2423        cx: &mut ViewContext<Self>,
2424    ) {
2425        let buffer_snapshot = self.editor.read(cx).buffer().read(cx).snapshot(cx);
2426        let (&excerpt_id, _, _) = buffer_snapshot.as_singleton().unwrap();
2427
2428        let Some(step) = self
2429            .context
2430            .read(cx)
2431            .workflow_step_for_range(step_range.clone(), cx)
2432        else {
2433            return;
2434        };
2435
2436        let resolved_step = step.read(cx).resolution.clone();
2437        if let Some(existing_step) = self.workflow_steps.get_mut(&step_range) {
2438            existing_step.resolved_step = resolved_step;
2439        } else {
2440            let start = buffer_snapshot
2441                .anchor_in_excerpt(excerpt_id, step_range.start)
2442                .unwrap();
2443            let end = buffer_snapshot
2444                .anchor_in_excerpt(excerpt_id, step_range.end)
2445                .unwrap();
2446            let weak_self = cx.view().downgrade();
2447            let block_ids = self.editor.update(cx, |editor, cx| {
2448                let step_range = step_range.clone();
2449                let editor_focus_handle = editor.focus_handle(cx);
2450                editor.insert_blocks(
2451                    vec![
2452                        BlockProperties {
2453                            position: start,
2454                            height: 1,
2455                            style: BlockStyle::Sticky,
2456                            render: Box::new({
2457                                let weak_self = weak_self.clone();
2458                                let step_range = step_range.clone();
2459                                move |cx| {
2460                                    let current_status = weak_self
2461                                        .update(&mut **cx, |context_editor, cx| {
2462                                            let step =
2463                                                context_editor.workflow_steps.get(&step_range)?;
2464                                            Some(step.status(cx))
2465                                        })
2466                                        .ok()
2467                                        .flatten();
2468
2469                                    let theme = cx.theme().status();
2470                                    let border_color = if current_status
2471                                        .as_ref()
2472                                        .map_or(false, |status| status.is_confirmed())
2473                                    {
2474                                        theme.ignored_border
2475                                    } else {
2476                                        theme.info_border
2477                                    };
2478                                    let step_index = weak_self
2479                                        .update(&mut **cx, |this, cx| {
2480                                            let snapshot = this
2481                                                .editor
2482                                                .read(cx)
2483                                                .buffer()
2484                                                .read(cx)
2485                                                .as_singleton()?
2486                                                .read(cx)
2487                                                .text_snapshot();
2488                                            let start_offset =
2489                                                step_range.start.to_offset(&snapshot);
2490                                            let parent_message = this
2491                                                .context
2492                                                .read(cx)
2493                                                .messages_for_offsets([start_offset], cx);
2494                                            debug_assert_eq!(parent_message.len(), 1);
2495                                            let parent_message = parent_message.first()?;
2496
2497                                            let index_of_current_step = this
2498                                                .workflow_steps
2499                                                .keys()
2500                                                .filter(|workflow_step_range| {
2501                                                    workflow_step_range
2502                                                        .start
2503                                                        .cmp(&parent_message.anchor, &snapshot)
2504                                                        .is_ge()
2505                                                        && workflow_step_range
2506                                                            .end
2507                                                            .cmp(&step_range.end, &snapshot)
2508                                                            .is_le()
2509                                                })
2510                                                .count();
2511                                            Some(index_of_current_step)
2512                                        })
2513                                        .ok()
2514                                        .flatten();
2515
2516                                    let step_label = if let Some(index) = step_index {
2517                                        Label::new(format!("Step {index}")).size(LabelSize::Small)
2518                                    } else {
2519                                        Label::new("Step").size(LabelSize::Small)
2520                                    };
2521
2522                                    let step_label = if current_status
2523                                        .as_ref()
2524                                        .is_some_and(|status| status.is_confirmed())
2525                                    {
2526                                        h_flex()
2527                                            .items_center()
2528                                            .gap_2()
2529                                            .child(
2530                                                step_label.strikethrough(true).color(Color::Muted),
2531                                            )
2532                                            .child(
2533                                                Icon::new(IconName::Check)
2534                                                    .size(IconSize::Small)
2535                                                    .color(Color::Created),
2536                                            )
2537                                    } else {
2538                                        div().child(step_label)
2539                                    };
2540
2541                                    let step_label_element = step_label.into_any_element();
2542
2543                                    let step_label = h_flex()
2544                                        .id("step")
2545                                        .group("step-label")
2546                                        .items_center()
2547                                        .gap_1()
2548                                        .child(step_label_element)
2549                                        .child(
2550                                            IconButton::new("edit-step", IconName::SearchCode)
2551                                                .size(ButtonSize::Compact)
2552                                                .icon_size(IconSize::Small)
2553                                                .shape(IconButtonShape::Square)
2554                                                .visible_on_hover("step-label")
2555                                                .tooltip(|cx| Tooltip::text("Open Step View", cx))
2556                                                .on_click({
2557                                                    let this = weak_self.clone();
2558                                                    let step_range = step_range.clone();
2559                                                    move |_, cx| {
2560                                                        this.update(cx, |this, cx| {
2561                                                            this.open_workflow_step(
2562                                                                step_range.clone(),
2563                                                                cx,
2564                                                            );
2565                                                        })
2566                                                        .ok();
2567                                                    }
2568                                                }),
2569                                        );
2570
2571                                    div()
2572                                        .w_full()
2573                                        .px(cx.gutter_dimensions.full_width())
2574                                        .child(
2575                                            h_flex()
2576                                                .w_full()
2577                                                .h_8()
2578                                                .border_b_1()
2579                                                .border_color(border_color)
2580                                                .pb_2()
2581                                                .items_center()
2582                                                .justify_between()
2583                                                .gap_2()
2584                                                .child(
2585                                                    h_flex()
2586                                                        .justify_start()
2587                                                        .gap_2()
2588                                                        .child(step_label),
2589                                                )
2590                                                .children(current_status.as_ref().map(|status| {
2591                                                    h_flex().w_full().justify_end().child(
2592                                                        status.into_element(
2593                                                            step_range.clone(),
2594                                                            editor_focus_handle.clone(),
2595                                                            weak_self.clone(),
2596                                                            cx,
2597                                                        ),
2598                                                    )
2599                                                })),
2600                                        )
2601                                        .into_any()
2602                                }
2603                            }),
2604                            disposition: BlockDisposition::Above,
2605                            priority: 0,
2606                        },
2607                        BlockProperties {
2608                            position: end,
2609                            height: 0,
2610                            style: BlockStyle::Sticky,
2611                            render: Box::new(move |cx| {
2612                                let current_status = weak_self
2613                                    .update(&mut **cx, |context_editor, cx| {
2614                                        let step =
2615                                            context_editor.workflow_steps.get(&step_range)?;
2616                                        Some(step.status(cx))
2617                                    })
2618                                    .ok()
2619                                    .flatten();
2620                                let theme = cx.theme().status();
2621                                let border_color = if current_status
2622                                    .as_ref()
2623                                    .map_or(false, |status| status.is_confirmed())
2624                                {
2625                                    theme.ignored_border
2626                                } else {
2627                                    theme.info_border
2628                                };
2629
2630                                div()
2631                                    .w_full()
2632                                    .px(cx.gutter_dimensions.full_width())
2633                                    .child(h_flex().h(px(1.)).bg(border_color))
2634                                    .into_any()
2635                            }),
2636                            disposition: BlockDisposition::Below,
2637                            priority: 0,
2638                        },
2639                    ],
2640                    None,
2641                    cx,
2642                )
2643            });
2644            self.workflow_steps.insert(
2645                step_range.clone(),
2646                WorkflowStep {
2647                    range: step_range.clone(),
2648                    header_block_id: block_ids[0],
2649                    footer_block_id: block_ids[1],
2650                    resolved_step,
2651                    assist: None,
2652                },
2653            );
2654        }
2655
2656        self.update_active_workflow_step(cx);
2657    }
2658
2659    fn open_workflow_step(
2660        &mut self,
2661        step_range: Range<language::Anchor>,
2662        cx: &mut ViewContext<Self>,
2663    ) -> Option<()> {
2664        let pane = self
2665            .assistant_panel
2666            .update(cx, |panel, _| panel.pane())
2667            .ok()??;
2668        let context = self.context.read(cx);
2669        let language_registry = context.language_registry();
2670        let step = context.workflow_step_for_range(step_range, cx)?;
2671        let context = self.context.clone();
2672        cx.deref_mut().defer(move |cx| {
2673            pane.update(cx, |pane, cx| {
2674                let existing_item = pane
2675                    .items_of_type::<WorkflowStepView>()
2676                    .find(|item| *item.read(cx).step() == step.downgrade());
2677                if let Some(item) = existing_item {
2678                    if let Some(index) = pane.index_for_item(&item) {
2679                        pane.activate_item(index, true, true, cx);
2680                    }
2681                } else {
2682                    let view = cx
2683                        .new_view(|cx| WorkflowStepView::new(context, step, language_registry, cx));
2684                    pane.add_item(Box::new(view), true, true, None, cx);
2685                }
2686            });
2687        });
2688        None
2689    }
2690
2691    fn update_active_workflow_step(&mut self, cx: &mut ViewContext<Self>) {
2692        let new_step = self.active_workflow_step_for_cursor(cx);
2693        if new_step.as_ref() != self.active_workflow_step.as_ref() {
2694            let mut old_editor = None;
2695            let mut old_editor_was_open = None;
2696            if let Some(old_step) = self.active_workflow_step.take() {
2697                (old_editor, old_editor_was_open) =
2698                    self.hide_workflow_step(old_step.range, cx).unzip();
2699            }
2700
2701            let mut new_editor = None;
2702            if let Some(new_step) = new_step {
2703                new_editor = self.show_workflow_step(new_step.range.clone(), cx);
2704                self.active_workflow_step = Some(new_step);
2705            }
2706
2707            if new_editor != old_editor {
2708                if let Some((old_editor, old_editor_was_open)) = old_editor.zip(old_editor_was_open)
2709                {
2710                    self.close_workflow_editor(cx, old_editor, old_editor_was_open)
2711                }
2712            }
2713        }
2714    }
2715
2716    fn hide_workflow_step(
2717        &mut self,
2718        step_range: Range<language::Anchor>,
2719        cx: &mut ViewContext<Self>,
2720    ) -> Option<(View<Editor>, bool)> {
2721        let Some(step) = self.workflow_steps.get_mut(&step_range) else {
2722            return None;
2723        };
2724        let Some(assist) = step.assist.as_ref() else {
2725            return None;
2726        };
2727        let Some(editor) = assist.editor.upgrade() else {
2728            return None;
2729        };
2730
2731        if matches!(step.status(cx), WorkflowStepStatus::Idle) {
2732            let assist = step.assist.take().unwrap();
2733            InlineAssistant::update_global(cx, |assistant, cx| {
2734                for assist_id in assist.assist_ids {
2735                    assistant.finish_assist(assist_id, true, cx)
2736                }
2737            });
2738            return Some((editor, assist.editor_was_open));
2739        }
2740
2741        return None;
2742    }
2743
2744    fn close_workflow_editor(
2745        &mut self,
2746        cx: &mut ViewContext<ContextEditor>,
2747        editor: View<Editor>,
2748        editor_was_open: bool,
2749    ) {
2750        self.workspace
2751            .update(cx, |workspace, cx| {
2752                if let Some(pane) = workspace.pane_for(&editor) {
2753                    pane.update(cx, |pane, cx| {
2754                        let item_id = editor.entity_id();
2755                        if !editor_was_open && pane.is_active_preview_item(item_id) {
2756                            pane.close_item_by_id(item_id, SaveIntent::Skip, cx)
2757                                .detach_and_log_err(cx);
2758                        }
2759                    });
2760                }
2761            })
2762            .ok();
2763    }
2764
2765    fn show_workflow_step(
2766        &mut self,
2767        step_range: Range<language::Anchor>,
2768        cx: &mut ViewContext<Self>,
2769    ) -> Option<View<Editor>> {
2770        let Some(step) = self.workflow_steps.get_mut(&step_range) else {
2771            return None;
2772        };
2773        let mut editor_to_return = None;
2774        let mut scroll_to_assist_id = None;
2775        match step.status(cx) {
2776            WorkflowStepStatus::Idle => {
2777                if let Some(assist) = step.assist.as_ref() {
2778                    scroll_to_assist_id = assist.assist_ids.first().copied();
2779                } else if let Some(Ok(resolved)) = step.resolved_step.as_ref() {
2780                    step.assist = Self::open_assists_for_step(
2781                        resolved,
2782                        &self.project,
2783                        &self.assistant_panel,
2784                        &self.workspace,
2785                        cx,
2786                    );
2787                    editor_to_return = step
2788                        .assist
2789                        .as_ref()
2790                        .and_then(|assist| assist.editor.upgrade());
2791                }
2792            }
2793            WorkflowStepStatus::Pending => {
2794                if let Some(assist) = step.assist.as_ref() {
2795                    let assistant = InlineAssistant::global(cx);
2796                    scroll_to_assist_id = assist
2797                        .assist_ids
2798                        .iter()
2799                        .copied()
2800                        .find(|assist_id| assistant.assist_status(*assist_id, cx).is_pending());
2801                }
2802            }
2803            WorkflowStepStatus::Done => {
2804                if let Some(assist) = step.assist.as_ref() {
2805                    scroll_to_assist_id = assist.assist_ids.first().copied();
2806                }
2807            }
2808            _ => {}
2809        }
2810
2811        if let Some(assist_id) = scroll_to_assist_id {
2812            if let Some(assist_editor) = step
2813                .assist
2814                .as_ref()
2815                .and_then(|assists| assists.editor.upgrade())
2816            {
2817                editor_to_return = Some(assist_editor.clone());
2818                self.workspace
2819                    .update(cx, |workspace, cx| {
2820                        workspace.activate_item(&assist_editor, false, false, cx);
2821                    })
2822                    .ok();
2823                InlineAssistant::update_global(cx, |assistant, cx| {
2824                    assistant.scroll_to_assist(assist_id, cx)
2825                });
2826            }
2827        }
2828
2829        return editor_to_return;
2830    }
2831
2832    fn open_assists_for_step(
2833        resolved_step: &WorkflowStepResolution,
2834        project: &Model<Project>,
2835        assistant_panel: &WeakView<AssistantPanel>,
2836        workspace: &WeakView<Workspace>,
2837        cx: &mut ViewContext<Self>,
2838    ) -> Option<WorkflowAssist> {
2839        let assistant_panel = assistant_panel.upgrade()?;
2840        if resolved_step.suggestion_groups.is_empty() {
2841            return None;
2842        }
2843
2844        let editor;
2845        let mut editor_was_open = false;
2846        let mut suggestion_groups = Vec::new();
2847        if resolved_step.suggestion_groups.len() == 1
2848            && resolved_step
2849                .suggestion_groups
2850                .values()
2851                .next()
2852                .unwrap()
2853                .len()
2854                == 1
2855        {
2856            // If there's only one buffer and one suggestion group, open it directly
2857            let (buffer, groups) = resolved_step.suggestion_groups.iter().next().unwrap();
2858            let group = groups.into_iter().next().unwrap();
2859            editor = workspace
2860                .update(cx, |workspace, cx| {
2861                    let active_pane = workspace.active_pane().clone();
2862                    editor_was_open =
2863                        workspace.is_project_item_open::<Editor>(&active_pane, buffer, cx);
2864                    workspace.open_project_item::<Editor>(
2865                        active_pane,
2866                        buffer.clone(),
2867                        false,
2868                        false,
2869                        cx,
2870                    )
2871                })
2872                .log_err()?;
2873            let (&excerpt_id, _, _) = editor
2874                .read(cx)
2875                .buffer()
2876                .read(cx)
2877                .read(cx)
2878                .as_singleton()
2879                .unwrap();
2880
2881            // Scroll the editor to the suggested assist
2882            editor.update(cx, |editor, cx| {
2883                let multibuffer = editor.buffer().read(cx).snapshot(cx);
2884                let (&excerpt_id, _, buffer) = multibuffer.as_singleton().unwrap();
2885                let anchor = if group.context_range.start.to_offset(buffer) == 0 {
2886                    Anchor::min()
2887                } else {
2888                    multibuffer
2889                        .anchor_in_excerpt(excerpt_id, group.context_range.start)
2890                        .unwrap()
2891                };
2892
2893                editor.set_scroll_anchor(
2894                    ScrollAnchor {
2895                        offset: gpui::Point::default(),
2896                        anchor,
2897                    },
2898                    cx,
2899                );
2900            });
2901
2902            suggestion_groups.push((excerpt_id, group));
2903        } else {
2904            // If there are multiple buffers or suggestion groups, create a multibuffer
2905            let multibuffer = cx.new_model(|cx| {
2906                let replica_id = project.read(cx).replica_id();
2907                let mut multibuffer = MultiBuffer::new(replica_id, Capability::ReadWrite)
2908                    .with_title(resolved_step.title.clone());
2909                for (buffer, groups) in &resolved_step.suggestion_groups {
2910                    let excerpt_ids = multibuffer.push_excerpts(
2911                        buffer.clone(),
2912                        groups.iter().map(|suggestion_group| ExcerptRange {
2913                            context: suggestion_group.context_range.clone(),
2914                            primary: None,
2915                        }),
2916                        cx,
2917                    );
2918                    suggestion_groups.extend(excerpt_ids.into_iter().zip(groups));
2919                }
2920                multibuffer
2921            });
2922
2923            editor = cx.new_view(|cx| {
2924                Editor::for_multibuffer(multibuffer, Some(project.clone()), true, cx)
2925            });
2926            workspace
2927                .update(cx, |workspace, cx| {
2928                    workspace.add_item_to_active_pane(Box::new(editor.clone()), None, false, cx)
2929                })
2930                .log_err()?;
2931        }
2932
2933        let mut assist_ids = Vec::new();
2934        for (excerpt_id, suggestion_group) in suggestion_groups {
2935            for suggestion in &suggestion_group.suggestions {
2936                assist_ids.extend(suggestion.show(
2937                    &editor,
2938                    excerpt_id,
2939                    workspace,
2940                    &assistant_panel,
2941                    cx,
2942                ));
2943            }
2944        }
2945
2946        let mut observations = Vec::new();
2947        InlineAssistant::update_global(cx, |assistant, _cx| {
2948            for assist_id in &assist_ids {
2949                observations.push(assistant.observe_assist(*assist_id));
2950            }
2951        });
2952
2953        Some(WorkflowAssist {
2954            assist_ids,
2955            editor: editor.downgrade(),
2956            editor_was_open,
2957            _observe_assist_status: cx.spawn(|this, mut cx| async move {
2958                while !observations.is_empty() {
2959                    let (result, ix, _) = futures::future::select_all(
2960                        observations
2961                            .iter_mut()
2962                            .map(|observation| Box::pin(observation.changed())),
2963                    )
2964                    .await;
2965
2966                    if result.is_err() {
2967                        observations.remove(ix);
2968                    }
2969
2970                    if this.update(&mut cx, |_, cx| cx.notify()).is_err() {
2971                        break;
2972                    }
2973                }
2974            }),
2975        })
2976    }
2977
2978    fn handle_editor_search_event(
2979        &mut self,
2980        _: View<Editor>,
2981        event: &SearchEvent,
2982        cx: &mut ViewContext<Self>,
2983    ) {
2984        cx.emit(event.clone());
2985    }
2986
2987    fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
2988        self.editor.update(cx, |editor, cx| {
2989            let snapshot = editor.snapshot(cx);
2990            let cursor = editor.selections.newest_anchor().head();
2991            let cursor_row = cursor
2992                .to_display_point(&snapshot.display_snapshot)
2993                .row()
2994                .as_f32();
2995            let scroll_position = editor
2996                .scroll_manager
2997                .anchor()
2998                .scroll_position(&snapshot.display_snapshot);
2999
3000            let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
3001            if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
3002                Some(ScrollPosition {
3003                    cursor,
3004                    offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
3005                })
3006            } else {
3007                None
3008            }
3009        })
3010    }
3011
3012    fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
3013        self.editor.update(cx, |editor, cx| {
3014            let buffer = editor.buffer().read(cx).snapshot(cx);
3015            let excerpt_id = *buffer.as_singleton().unwrap().0;
3016            let old_blocks = std::mem::take(&mut self.blocks);
3017            let new_blocks = self
3018                .context
3019                .read(cx)
3020                .messages(cx)
3021                .map(|message| BlockProperties {
3022                    position: buffer
3023                        .anchor_in_excerpt(excerpt_id, message.anchor)
3024                        .unwrap(),
3025                    height: 2,
3026                    style: BlockStyle::Sticky,
3027                    render: Box::new({
3028                        let context = self.context.clone();
3029                        move |cx| {
3030                            let message_id = message.id;
3031                            let show_spinner = message.role == Role::Assistant
3032                                && message.status == MessageStatus::Pending;
3033
3034                            let label = match message.role {
3035                                Role::User => {
3036                                    Label::new("You").color(Color::Default).into_any_element()
3037                                }
3038                                Role::Assistant => {
3039                                    let label = Label::new("Assistant").color(Color::Info);
3040                                    if show_spinner {
3041                                        label
3042                                            .with_animation(
3043                                                "pulsating-label",
3044                                                Animation::new(Duration::from_secs(2))
3045                                                    .repeat()
3046                                                    .with_easing(pulsating_between(0.4, 0.8)),
3047                                                |label, delta| label.alpha(delta),
3048                                            )
3049                                            .into_any_element()
3050                                    } else {
3051                                        label.into_any_element()
3052                                    }
3053                                }
3054
3055                                Role::System => Label::new("System")
3056                                    .color(Color::Warning)
3057                                    .into_any_element(),
3058                            };
3059
3060                            let sender = ButtonLike::new("role")
3061                                .style(ButtonStyle::Filled)
3062                                .child(label)
3063                                .tooltip(|cx| {
3064                                    Tooltip::with_meta(
3065                                        "Toggle message role",
3066                                        None,
3067                                        "Available roles: You (User), Assistant, System",
3068                                        cx,
3069                                    )
3070                                })
3071                                .on_click({
3072                                    let context = context.clone();
3073                                    move |_, cx| {
3074                                        context.update(cx, |context, cx| {
3075                                            context.cycle_message_roles(
3076                                                HashSet::from_iter(Some(message_id)),
3077                                                cx,
3078                                            )
3079                                        })
3080                                    }
3081                                });
3082
3083                            h_flex()
3084                                .id(("message_header", message_id.as_u64()))
3085                                .pl(cx.gutter_dimensions.full_width())
3086                                .h_11()
3087                                .w_full()
3088                                .relative()
3089                                .gap_1()
3090                                .child(sender)
3091                                .children(match &message.status {
3092                                    MessageStatus::Error(error) => Some(
3093                                        Button::new("show-error", "Error")
3094                                            .color(Color::Error)
3095                                            .selected_label_color(Color::Error)
3096                                            .selected_icon_color(Color::Error)
3097                                            .icon(IconName::XCircle)
3098                                            .icon_color(Color::Error)
3099                                            .icon_size(IconSize::Small)
3100                                            .icon_position(IconPosition::Start)
3101                                            .tooltip(move |cx| {
3102                                                Tooltip::with_meta(
3103                                                    "Error interacting with language model",
3104                                                    None,
3105                                                    "Click for more details",
3106                                                    cx,
3107                                                )
3108                                            })
3109                                            .on_click({
3110                                                let context = context.clone();
3111                                                let error = error.clone();
3112                                                move |_, cx| {
3113                                                    context.update(cx, |_, cx| {
3114                                                        cx.emit(ContextEvent::ShowAssistError(
3115                                                            error.clone(),
3116                                                        ));
3117                                                    });
3118                                                }
3119                                            })
3120                                            .into_any_element(),
3121                                    ),
3122                                    MessageStatus::Canceled => Some(
3123                                        ButtonLike::new("canceled")
3124                                            .child(
3125                                                Icon::new(IconName::XCircle).color(Color::Disabled),
3126                                            )
3127                                            .child(
3128                                                Label::new("Canceled")
3129                                                    .size(LabelSize::Small)
3130                                                    .color(Color::Disabled),
3131                                            )
3132                                            .tooltip(move |cx| {
3133                                                Tooltip::with_meta(
3134                                                    "Canceled",
3135                                                    None,
3136                                                    "Interaction with the assistant was canceled",
3137                                                    cx,
3138                                                )
3139                                            })
3140                                            .into_any_element(),
3141                                    ),
3142                                    _ => None,
3143                                })
3144                                .into_any_element()
3145                        }
3146                    }),
3147                    disposition: BlockDisposition::Above,
3148                    priority: usize::MAX,
3149                })
3150                .collect::<Vec<_>>();
3151
3152            editor.remove_blocks(old_blocks, None, cx);
3153            let ids = editor.insert_blocks(new_blocks, None, cx);
3154            self.blocks = HashSet::from_iter(ids);
3155        });
3156    }
3157
3158    fn insert_selection(
3159        workspace: &mut Workspace,
3160        _: &InsertIntoEditor,
3161        cx: &mut ViewContext<Workspace>,
3162    ) {
3163        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3164            return;
3165        };
3166        let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
3167            return;
3168        };
3169        let Some(active_editor_view) = workspace
3170            .active_item(cx)
3171            .and_then(|item| item.act_as::<Editor>(cx))
3172        else {
3173            return;
3174        };
3175
3176        let context_editor = context_editor_view.read(cx).editor.read(cx);
3177        let anchor = context_editor.selections.newest_anchor();
3178        let text = context_editor
3179            .buffer()
3180            .read(cx)
3181            .read(cx)
3182            .text_for_range(anchor.range())
3183            .collect::<String>();
3184
3185        // If nothing is selected, don't delete the current selection; instead, be a no-op.
3186        if !text.is_empty() {
3187            active_editor_view.update(cx, |editor, cx| {
3188                editor.insert(&text, cx);
3189                editor.focus(cx);
3190            })
3191        }
3192    }
3193
3194    fn quote_selection(
3195        workspace: &mut Workspace,
3196        _: &QuoteSelection,
3197        cx: &mut ViewContext<Workspace>,
3198    ) {
3199        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3200            return;
3201        };
3202        let Some(editor) = workspace
3203            .active_item(cx)
3204            .and_then(|item| item.act_as::<Editor>(cx))
3205        else {
3206            return;
3207        };
3208
3209        let selection = editor.update(cx, |editor, cx| editor.selections.newest_adjusted(cx));
3210        let editor = editor.read(cx);
3211        let buffer = editor.buffer().read(cx).snapshot(cx);
3212        let range = editor::ToOffset::to_offset(&selection.start, &buffer)
3213            ..editor::ToOffset::to_offset(&selection.end, &buffer);
3214        let selected_text = buffer.text_for_range(range.clone()).collect::<String>();
3215        if selected_text.is_empty() {
3216            return;
3217        }
3218
3219        let start_language = buffer.language_at(range.start);
3220        let end_language = buffer.language_at(range.end);
3221        let language_name = if start_language == end_language {
3222            start_language.map(|language| language.code_fence_block_name())
3223        } else {
3224            None
3225        };
3226        let language_name = language_name.as_deref().unwrap_or("");
3227
3228        let filename = buffer
3229            .file_at(selection.start)
3230            .map(|file| file.full_path(cx));
3231
3232        let text = if language_name == "markdown" {
3233            selected_text
3234                .lines()
3235                .map(|line| format!("> {}", line))
3236                .collect::<Vec<_>>()
3237                .join("\n")
3238        } else {
3239            let start_symbols = buffer
3240                .symbols_containing(selection.start, None)
3241                .map(|(_, symbols)| symbols);
3242            let end_symbols = buffer
3243                .symbols_containing(selection.end, None)
3244                .map(|(_, symbols)| symbols);
3245
3246            let outline_text =
3247                if let Some((start_symbols, end_symbols)) = start_symbols.zip(end_symbols) {
3248                    Some(
3249                        start_symbols
3250                            .into_iter()
3251                            .zip(end_symbols)
3252                            .take_while(|(a, b)| a == b)
3253                            .map(|(a, _)| a.text)
3254                            .collect::<Vec<_>>()
3255                            .join(" > "),
3256                    )
3257                } else {
3258                    None
3259                };
3260
3261            let line_comment_prefix = start_language
3262                .and_then(|l| l.default_scope().line_comment_prefixes().first().cloned());
3263
3264            let fence = codeblock_fence_for_path(
3265                filename.as_deref(),
3266                Some(selection.start.row..selection.end.row),
3267            );
3268
3269            if let Some((line_comment_prefix, outline_text)) = line_comment_prefix.zip(outline_text)
3270            {
3271                let breadcrumb = format!("{line_comment_prefix}Excerpt from: {outline_text}\n");
3272                format!("{fence}{breadcrumb}{selected_text}\n```")
3273            } else {
3274                format!("{fence}{selected_text}\n```")
3275            }
3276        };
3277
3278        let crease_title = if let Some(path) = filename {
3279            let start_line = selection.start.row + 1;
3280            let end_line = selection.end.row + 1;
3281            if start_line == end_line {
3282                format!("{}, Line {}", path.display(), start_line)
3283            } else {
3284                format!("{}, Lines {} to {}", path.display(), start_line, end_line)
3285            }
3286        } else {
3287            "Quoted selection".to_string()
3288        };
3289
3290        // Activate the panel
3291        if !panel.focus_handle(cx).contains_focused(cx) {
3292            workspace.toggle_panel_focus::<AssistantPanel>(cx);
3293        }
3294
3295        panel.update(cx, |_, cx| {
3296            // Wait to create a new context until the workspace is no longer
3297            // being updated.
3298            cx.defer(move |panel, cx| {
3299                if let Some(context) = panel
3300                    .active_context_editor(cx)
3301                    .or_else(|| panel.new_context(cx))
3302                {
3303                    context.update(cx, |context, cx| {
3304                        context.editor.update(cx, |editor, cx| {
3305                            editor.insert("\n", cx);
3306
3307                            let point = editor.selections.newest::<Point>(cx).head();
3308                            let start_row = MultiBufferRow(point.row);
3309
3310                            editor.insert(&text, cx);
3311
3312                            let snapshot = editor.buffer().read(cx).snapshot(cx);
3313                            let anchor_before = snapshot.anchor_after(point);
3314                            let anchor_after = editor
3315                                .selections
3316                                .newest_anchor()
3317                                .head()
3318                                .bias_left(&snapshot);
3319
3320                            editor.insert("\n", cx);
3321
3322                            let fold_placeholder = quote_selection_fold_placeholder(
3323                                crease_title,
3324                                cx.view().downgrade(),
3325                            );
3326                            let crease = Crease::new(
3327                                anchor_before..anchor_after,
3328                                fold_placeholder,
3329                                render_quote_selection_output_toggle,
3330                                |_, _, _| Empty.into_any(),
3331                            );
3332                            editor.insert_creases(vec![crease], cx);
3333                            editor.fold_at(
3334                                &FoldAt {
3335                                    buffer_row: start_row,
3336                                },
3337                                cx,
3338                            );
3339                        })
3340                    });
3341                };
3342            });
3343        });
3344    }
3345
3346    fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
3347        let editor = self.editor.read(cx);
3348        let context = self.context.read(cx);
3349        if editor.selections.count() == 1 {
3350            let selection = editor.selections.newest::<usize>(cx);
3351            let mut copied_text = String::new();
3352            let mut spanned_messages = 0;
3353            for message in context.messages(cx) {
3354                if message.offset_range.start >= selection.range().end {
3355                    break;
3356                } else if message.offset_range.end >= selection.range().start {
3357                    let range = cmp::max(message.offset_range.start, selection.range().start)
3358                        ..cmp::min(message.offset_range.end, selection.range().end);
3359                    if !range.is_empty() {
3360                        spanned_messages += 1;
3361                        write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
3362                        for chunk in context.buffer().read(cx).text_for_range(range) {
3363                            copied_text.push_str(chunk);
3364                        }
3365                        copied_text.push('\n');
3366                    }
3367                }
3368            }
3369
3370            if spanned_messages > 1 {
3371                cx.write_to_clipboard(ClipboardItem::new_string(copied_text));
3372                return;
3373            }
3374        }
3375
3376        cx.propagate();
3377    }
3378
3379    fn paste(&mut self, _: &editor::actions::Paste, cx: &mut ViewContext<Self>) {
3380        let images = if let Some(item) = cx.read_from_clipboard() {
3381            item.into_entries()
3382                .filter_map(|entry| {
3383                    if let ClipboardEntry::Image(image) = entry {
3384                        Some(image)
3385                    } else {
3386                        None
3387                    }
3388                })
3389                .collect()
3390        } else {
3391            Vec::new()
3392        };
3393
3394        if images.is_empty() {
3395            // If we didn't find any valid image data to paste, propagate to let normal pasting happen.
3396            cx.propagate();
3397        } else {
3398            let mut image_positions = Vec::new();
3399            self.editor.update(cx, |editor, cx| {
3400                editor.transact(cx, |editor, cx| {
3401                    let edits = editor
3402                        .selections
3403                        .all::<usize>(cx)
3404                        .into_iter()
3405                        .map(|selection| (selection.start..selection.end, "\n"));
3406                    editor.edit(edits, cx);
3407
3408                    let snapshot = editor.buffer().read(cx).snapshot(cx);
3409                    for selection in editor.selections.all::<usize>(cx) {
3410                        image_positions.push(snapshot.anchor_before(selection.end));
3411                    }
3412                });
3413            });
3414
3415            self.context.update(cx, |context, cx| {
3416                for image in images {
3417                    let image_id = image.id();
3418                    context.insert_image(image, cx);
3419                    for image_position in image_positions.iter() {
3420                        context.insert_image_anchor(image_id, image_position.text_anchor, cx);
3421                    }
3422                }
3423            });
3424        }
3425    }
3426
3427    fn update_image_blocks(&mut self, cx: &mut ViewContext<Self>) {
3428        self.editor.update(cx, |editor, cx| {
3429            let buffer = editor.buffer().read(cx).snapshot(cx);
3430            let excerpt_id = *buffer.as_singleton().unwrap().0;
3431            let old_blocks = std::mem::take(&mut self.image_blocks);
3432            let new_blocks = self
3433                .context
3434                .read(cx)
3435                .images(cx)
3436                .filter_map(|image| {
3437                    const MAX_HEIGHT_IN_LINES: u32 = 8;
3438                    let anchor = buffer.anchor_in_excerpt(excerpt_id, image.anchor).unwrap();
3439                    let image = image.render_image.clone();
3440                    anchor.is_valid(&buffer).then(|| BlockProperties {
3441                        position: anchor,
3442                        height: MAX_HEIGHT_IN_LINES,
3443                        style: BlockStyle::Sticky,
3444                        render: Box::new(move |cx| {
3445                            let image_size = size_for_image(
3446                                &image,
3447                                size(
3448                                    cx.max_width - cx.gutter_dimensions.full_width(),
3449                                    MAX_HEIGHT_IN_LINES as f32 * cx.line_height,
3450                                ),
3451                            );
3452                            h_flex()
3453                                .pl(cx.gutter_dimensions.full_width())
3454                                .child(
3455                                    img(image.clone())
3456                                        .object_fit(gpui::ObjectFit::ScaleDown)
3457                                        .w(image_size.width)
3458                                        .h(image_size.height),
3459                                )
3460                                .into_any_element()
3461                        }),
3462
3463                        disposition: BlockDisposition::Above,
3464                        priority: 0,
3465                    })
3466                })
3467                .collect::<Vec<_>>();
3468
3469            editor.remove_blocks(old_blocks, None, cx);
3470            let ids = editor.insert_blocks(new_blocks, None, cx);
3471            self.image_blocks = HashSet::from_iter(ids);
3472        });
3473    }
3474
3475    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
3476        self.context.update(cx, |context, cx| {
3477            let selections = self.editor.read(cx).selections.disjoint_anchors();
3478            for selection in selections.as_ref() {
3479                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
3480                let range = selection
3481                    .map(|endpoint| endpoint.to_offset(&buffer))
3482                    .range();
3483                context.split_message(range, cx);
3484            }
3485        });
3486    }
3487
3488    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
3489        self.context.update(cx, |context, cx| {
3490            context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
3491        });
3492    }
3493
3494    fn title(&self, cx: &AppContext) -> Cow<str> {
3495        self.context
3496            .read(cx)
3497            .summary()
3498            .map(|summary| summary.text.clone())
3499            .map(Cow::Owned)
3500            .unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
3501    }
3502
3503    fn render_notice(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
3504        use feature_flags::FeatureFlagAppExt;
3505        let nudge = self.assistant_panel.upgrade().map(|assistant_panel| {
3506            assistant_panel.read(cx).show_zed_ai_notice && cx.has_flag::<feature_flags::ZedPro>()
3507        });
3508
3509        if nudge.map_or(false, |value| value) {
3510            Some(
3511                h_flex()
3512                    .p_3()
3513                    .border_b_1()
3514                    .border_color(cx.theme().colors().border_variant)
3515                    .bg(cx.theme().colors().editor_background)
3516                    .justify_between()
3517                    .child(
3518                        h_flex()
3519                            .gap_3()
3520                            .child(Icon::new(IconName::ZedAssistant).color(Color::Accent))
3521                            .child(Label::new("Zed AI is here! Get started by signing in →")),
3522                    )
3523                    .child(
3524                        Button::new("sign-in", "Sign in")
3525                            .size(ButtonSize::Compact)
3526                            .style(ButtonStyle::Filled)
3527                            .on_click(cx.listener(|this, _event, cx| {
3528                                let client = this
3529                                    .workspace
3530                                    .update(cx, |workspace, _| workspace.client().clone())
3531                                    .log_err();
3532
3533                                if let Some(client) = client {
3534                                    cx.spawn(|this, mut cx| async move {
3535                                        client.authenticate_and_connect(true, &mut cx).await?;
3536                                        this.update(&mut cx, |_, cx| cx.notify())
3537                                    })
3538                                    .detach_and_log_err(cx)
3539                                }
3540                            })),
3541                    )
3542                    .into_any_element(),
3543            )
3544        } else if let Some(configuration_error) = configuration_error(cx) {
3545            let label = match configuration_error {
3546                ConfigurationError::NoProvider => "No LLM provider selected.",
3547                ConfigurationError::ProviderNotAuthenticated => "LLM provider is not configured.",
3548            };
3549            Some(
3550                h_flex()
3551                    .px_3()
3552                    .py_2()
3553                    .border_b_1()
3554                    .border_color(cx.theme().colors().border_variant)
3555                    .bg(cx.theme().colors().editor_background)
3556                    .justify_between()
3557                    .child(
3558                        h_flex()
3559                            .gap_3()
3560                            .child(
3561                                Icon::new(IconName::ExclamationTriangle)
3562                                    .size(IconSize::Small)
3563                                    .color(Color::Warning),
3564                            )
3565                            .child(Label::new(label)),
3566                    )
3567                    .child(
3568                        Button::new("open-configuration", "Open configuration")
3569                            .size(ButtonSize::Compact)
3570                            .icon_size(IconSize::Small)
3571                            .style(ButtonStyle::Filled)
3572                            .on_click({
3573                                let focus_handle = self.focus_handle(cx).clone();
3574                                move |_event, cx| {
3575                                    focus_handle.dispatch_action(&ShowConfiguration, cx);
3576                                }
3577                            }),
3578                    )
3579                    .into_any_element(),
3580            )
3581        } else {
3582            None
3583        }
3584    }
3585
3586    fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3587        let focus_handle = self.focus_handle(cx).clone();
3588        let button_text = match self.active_workflow_step() {
3589            Some(step) => match step.status(cx) {
3590                WorkflowStepStatus::Resolving => "Resolving Step...",
3591                WorkflowStepStatus::Empty | WorkflowStepStatus::Error(_) => "Retry Step Resolution",
3592                WorkflowStepStatus::Idle => "Transform",
3593                WorkflowStepStatus::Pending => "Transforming...",
3594                WorkflowStepStatus::Done => "Accept Transformation",
3595                WorkflowStepStatus::Confirmed => "Send",
3596            },
3597            None => "Send",
3598        };
3599
3600        let (style, tooltip) = match token_state(&self.context, cx) {
3601            Some(TokenState::NoTokensLeft { .. }) => (
3602                ButtonStyle::Tinted(TintColor::Negative),
3603                Some(Tooltip::text("Token limit reached", cx)),
3604            ),
3605            Some(TokenState::HasMoreTokens {
3606                over_warn_threshold,
3607                ..
3608            }) => {
3609                let (style, tooltip) = if over_warn_threshold {
3610                    (
3611                        ButtonStyle::Tinted(TintColor::Warning),
3612                        Some(Tooltip::text("Token limit is close to exhaustion", cx)),
3613                    )
3614                } else {
3615                    (ButtonStyle::Filled, None)
3616                };
3617                (style, tooltip)
3618            }
3619            None => (ButtonStyle::Filled, None),
3620        };
3621
3622        let provider = LanguageModelRegistry::read_global(cx).active_provider();
3623
3624        let has_configuration_error = configuration_error(cx).is_some();
3625        let needs_to_accept_terms = self.show_accept_terms
3626            && provider
3627                .as_ref()
3628                .map_or(false, |provider| provider.must_accept_terms(cx));
3629        let disabled = has_configuration_error || needs_to_accept_terms;
3630
3631        ButtonLike::new("send_button")
3632            .disabled(disabled)
3633            .style(style)
3634            .when_some(tooltip, |button, tooltip| {
3635                button.tooltip(move |_| tooltip.clone())
3636            })
3637            .layer(ElevationIndex::ModalSurface)
3638            .child(Label::new(button_text))
3639            .children(
3640                KeyBinding::for_action_in(&Assist, &focus_handle, cx)
3641                    .map(|binding| binding.into_any_element()),
3642            )
3643            .on_click(move |_event, cx| {
3644                focus_handle.dispatch_action(&Assist, cx);
3645            })
3646    }
3647
3648    fn active_workflow_step_for_cursor(&self, cx: &AppContext) -> Option<ActiveWorkflowStep> {
3649        let newest_cursor = self.editor.read(cx).selections.newest::<usize>(cx).head();
3650        let context = self.context.read(cx);
3651        let (range, step) = context.workflow_step_containing(newest_cursor, cx)?;
3652        Some(ActiveWorkflowStep {
3653            resolved: step.read(cx).resolution.is_some(),
3654            range,
3655        })
3656    }
3657}
3658
3659impl EventEmitter<EditorEvent> for ContextEditor {}
3660impl EventEmitter<SearchEvent> for ContextEditor {}
3661
3662impl Render for ContextEditor {
3663    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3664        let provider = LanguageModelRegistry::read_global(cx).active_provider();
3665        let accept_terms = if self.show_accept_terms {
3666            provider
3667                .as_ref()
3668                .and_then(|provider| provider.render_accept_terms(cx))
3669        } else {
3670            None
3671        };
3672        let focus_handle = self
3673            .workspace
3674            .update(cx, |workspace, cx| {
3675                Some(workspace.active_item_as::<Editor>(cx)?.focus_handle(cx))
3676            })
3677            .ok()
3678            .flatten();
3679        v_flex()
3680            .key_context("ContextEditor")
3681            .capture_action(cx.listener(ContextEditor::cancel))
3682            .capture_action(cx.listener(ContextEditor::save))
3683            .capture_action(cx.listener(ContextEditor::copy))
3684            .capture_action(cx.listener(ContextEditor::paste))
3685            .capture_action(cx.listener(ContextEditor::cycle_message_role))
3686            .capture_action(cx.listener(ContextEditor::confirm_command))
3687            .on_action(cx.listener(ContextEditor::assist))
3688            .on_action(cx.listener(ContextEditor::split))
3689            .size_full()
3690            .children(self.render_notice(cx))
3691            .child(
3692                div()
3693                    .flex_grow()
3694                    .bg(cx.theme().colors().editor_background)
3695                    .child(self.editor.clone()),
3696            )
3697            .when_some(accept_terms, |this, element| {
3698                this.child(
3699                    div()
3700                        .absolute()
3701                        .right_3()
3702                        .bottom_12()
3703                        .max_w_96()
3704                        .py_2()
3705                        .px_3()
3706                        .elevation_2(cx)
3707                        .bg(cx.theme().colors().surface_background)
3708                        .occlude()
3709                        .child(element),
3710                )
3711            })
3712            .when_some(self.error_message.clone(), |this, error_message| {
3713                this.child(
3714                    div()
3715                        .absolute()
3716                        .right_3()
3717                        .bottom_12()
3718                        .max_w_96()
3719                        .py_2()
3720                        .px_3()
3721                        .elevation_2(cx)
3722                        .occlude()
3723                        .child(
3724                            v_flex()
3725                                .gap_0p5()
3726                                .child(
3727                                    h_flex()
3728                                        .gap_1p5()
3729                                        .items_center()
3730                                        .child(Icon::new(IconName::XCircle).color(Color::Error))
3731                                        .child(
3732                                            Label::new("Error interacting with language model")
3733                                                .weight(FontWeight::MEDIUM),
3734                                        ),
3735                                )
3736                                .child(
3737                                    div()
3738                                        .id("error-message")
3739                                        .max_h_24()
3740                                        .overflow_y_scroll()
3741                                        .child(Label::new(error_message)),
3742                                )
3743                                .child(h_flex().justify_end().mt_1().child(
3744                                    Button::new("dismiss", "Dismiss").on_click(cx.listener(
3745                                        |this, _, cx| {
3746                                            this.error_message = None;
3747                                            cx.notify();
3748                                        },
3749                                    )),
3750                                )),
3751                        ),
3752                )
3753            })
3754            .child(
3755                h_flex().w_full().relative().child(
3756                    h_flex()
3757                        .p_2()
3758                        .w_full()
3759                        .border_t_1()
3760                        .border_color(cx.theme().colors().border_variant)
3761                        .bg(cx.theme().colors().editor_background)
3762                        .child(
3763                            h_flex()
3764                                .gap_2()
3765                                .child(render_inject_context_menu(cx.view().downgrade(), cx))
3766                                .child(
3767                                    IconButton::new("quote-button", IconName::Quote)
3768                                        .icon_size(IconSize::Small)
3769                                        .on_click(|_, cx| {
3770                                            cx.dispatch_action(QuoteSelection.boxed_clone());
3771                                        })
3772                                        .tooltip(move |cx| {
3773                                            cx.new_view(|cx| {
3774                                                Tooltip::new("Insert Selection").key_binding(
3775                                                    focus_handle.as_ref().and_then(|handle| {
3776                                                        KeyBinding::for_action_in(
3777                                                            &QuoteSelection,
3778                                                            &handle,
3779                                                            cx,
3780                                                        )
3781                                                    }),
3782                                                )
3783                                            })
3784                                            .into()
3785                                        }),
3786                                ),
3787                        )
3788                        .child(
3789                            h_flex()
3790                                .w_full()
3791                                .justify_end()
3792                                .child(div().child(self.render_send_button(cx))),
3793                        ),
3794                ),
3795            )
3796    }
3797}
3798
3799impl FocusableView for ContextEditor {
3800    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3801        self.editor.focus_handle(cx)
3802    }
3803}
3804
3805impl Item for ContextEditor {
3806    type Event = editor::EditorEvent;
3807
3808    fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
3809        Some(util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into())
3810    }
3811
3812    fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
3813        match event {
3814            EditorEvent::Edited { .. } => {
3815                f(item::ItemEvent::Edit);
3816            }
3817            EditorEvent::TitleChanged => {
3818                f(item::ItemEvent::UpdateTab);
3819            }
3820            _ => {}
3821        }
3822    }
3823
3824    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
3825        Some(self.title(cx).to_string().into())
3826    }
3827
3828    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
3829        Some(Box::new(handle.clone()))
3830    }
3831
3832    fn set_nav_history(&mut self, nav_history: pane::ItemNavHistory, cx: &mut ViewContext<Self>) {
3833        self.editor.update(cx, |editor, cx| {
3834            Item::set_nav_history(editor, nav_history, cx)
3835        })
3836    }
3837
3838    fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
3839        self.editor
3840            .update(cx, |editor, cx| Item::navigate(editor, data, cx))
3841    }
3842
3843    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
3844        self.editor
3845            .update(cx, |editor, cx| Item::deactivated(editor, cx))
3846    }
3847}
3848
3849impl SearchableItem for ContextEditor {
3850    type Match = <Editor as SearchableItem>::Match;
3851
3852    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
3853        self.editor.update(cx, |editor, cx| {
3854            editor.clear_matches(cx);
3855        });
3856    }
3857
3858    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
3859        self.editor
3860            .update(cx, |editor, cx| editor.update_matches(matches, cx));
3861    }
3862
3863    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
3864        self.editor
3865            .update(cx, |editor, cx| editor.query_suggestion(cx))
3866    }
3867
3868    fn activate_match(
3869        &mut self,
3870        index: usize,
3871        matches: &[Self::Match],
3872        cx: &mut ViewContext<Self>,
3873    ) {
3874        self.editor.update(cx, |editor, cx| {
3875            editor.activate_match(index, matches, cx);
3876        });
3877    }
3878
3879    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
3880        self.editor
3881            .update(cx, |editor, cx| editor.select_matches(matches, cx));
3882    }
3883
3884    fn replace(
3885        &mut self,
3886        identifier: &Self::Match,
3887        query: &project::search::SearchQuery,
3888        cx: &mut ViewContext<Self>,
3889    ) {
3890        self.editor
3891            .update(cx, |editor, cx| editor.replace(identifier, query, cx));
3892    }
3893
3894    fn find_matches(
3895        &mut self,
3896        query: Arc<project::search::SearchQuery>,
3897        cx: &mut ViewContext<Self>,
3898    ) -> Task<Vec<Self::Match>> {
3899        self.editor
3900            .update(cx, |editor, cx| editor.find_matches(query, cx))
3901    }
3902
3903    fn active_match_index(
3904        &mut self,
3905        matches: &[Self::Match],
3906        cx: &mut ViewContext<Self>,
3907    ) -> Option<usize> {
3908        self.editor
3909            .update(cx, |editor, cx| editor.active_match_index(matches, cx))
3910    }
3911}
3912
3913impl FollowableItem for ContextEditor {
3914    fn remote_id(&self) -> Option<workspace::ViewId> {
3915        self.remote_id
3916    }
3917
3918    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
3919        let context = self.context.read(cx);
3920        Some(proto::view::Variant::ContextEditor(
3921            proto::view::ContextEditor {
3922                context_id: context.id().to_proto(),
3923                editor: if let Some(proto::view::Variant::Editor(proto)) =
3924                    self.editor.read(cx).to_state_proto(cx)
3925                {
3926                    Some(proto)
3927                } else {
3928                    None
3929                },
3930            },
3931        ))
3932    }
3933
3934    fn from_state_proto(
3935        workspace: View<Workspace>,
3936        id: workspace::ViewId,
3937        state: &mut Option<proto::view::Variant>,
3938        cx: &mut WindowContext,
3939    ) -> Option<Task<Result<View<Self>>>> {
3940        let proto::view::Variant::ContextEditor(_) = state.as_ref()? else {
3941            return None;
3942        };
3943        let Some(proto::view::Variant::ContextEditor(state)) = state.take() else {
3944            unreachable!()
3945        };
3946
3947        let context_id = ContextId::from_proto(state.context_id);
3948        let editor_state = state.editor?;
3949
3950        let (project, panel) = workspace.update(cx, |workspace, cx| {
3951            Some((
3952                workspace.project().clone(),
3953                workspace.panel::<AssistantPanel>(cx)?,
3954            ))
3955        })?;
3956
3957        let context_editor =
3958            panel.update(cx, |panel, cx| panel.open_remote_context(context_id, cx));
3959
3960        Some(cx.spawn(|mut cx| async move {
3961            let context_editor = context_editor.await?;
3962            context_editor
3963                .update(&mut cx, |context_editor, cx| {
3964                    context_editor.remote_id = Some(id);
3965                    context_editor.editor.update(cx, |editor, cx| {
3966                        editor.apply_update_proto(
3967                            &project,
3968                            proto::update_view::Variant::Editor(proto::update_view::Editor {
3969                                selections: editor_state.selections,
3970                                pending_selection: editor_state.pending_selection,
3971                                scroll_top_anchor: editor_state.scroll_top_anchor,
3972                                scroll_x: editor_state.scroll_y,
3973                                scroll_y: editor_state.scroll_y,
3974                                ..Default::default()
3975                            }),
3976                            cx,
3977                        )
3978                    })
3979                })?
3980                .await?;
3981            Ok(context_editor)
3982        }))
3983    }
3984
3985    fn to_follow_event(event: &Self::Event) -> Option<item::FollowEvent> {
3986        Editor::to_follow_event(event)
3987    }
3988
3989    fn add_event_to_update_proto(
3990        &self,
3991        event: &Self::Event,
3992        update: &mut Option<proto::update_view::Variant>,
3993        cx: &WindowContext,
3994    ) -> bool {
3995        self.editor
3996            .read(cx)
3997            .add_event_to_update_proto(event, update, cx)
3998    }
3999
4000    fn apply_update_proto(
4001        &mut self,
4002        project: &Model<Project>,
4003        message: proto::update_view::Variant,
4004        cx: &mut ViewContext<Self>,
4005    ) -> Task<Result<()>> {
4006        self.editor.update(cx, |editor, cx| {
4007            editor.apply_update_proto(project, message, cx)
4008        })
4009    }
4010
4011    fn is_project_item(&self, _cx: &WindowContext) -> bool {
4012        true
4013    }
4014
4015    fn set_leader_peer_id(
4016        &mut self,
4017        leader_peer_id: Option<proto::PeerId>,
4018        cx: &mut ViewContext<Self>,
4019    ) {
4020        self.editor.update(cx, |editor, cx| {
4021            editor.set_leader_peer_id(leader_peer_id, cx)
4022        })
4023    }
4024
4025    fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<item::Dedup> {
4026        if existing.context.read(cx).id() == self.context.read(cx).id() {
4027            Some(item::Dedup::KeepExisting)
4028        } else {
4029            None
4030        }
4031    }
4032}
4033
4034pub struct ContextEditorToolbarItem {
4035    fs: Arc<dyn Fs>,
4036    workspace: WeakView<Workspace>,
4037    active_context_editor: Option<WeakView<ContextEditor>>,
4038    model_summary_editor: View<Editor>,
4039    model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
4040}
4041
4042fn active_editor_focus_handle(
4043    workspace: &WeakView<Workspace>,
4044    cx: &WindowContext<'_>,
4045) -> Option<FocusHandle> {
4046    workspace.upgrade().and_then(|workspace| {
4047        Some(
4048            workspace
4049                .read(cx)
4050                .active_item_as::<Editor>(cx)?
4051                .focus_handle(cx),
4052        )
4053    })
4054}
4055
4056fn render_inject_context_menu(
4057    active_context_editor: WeakView<ContextEditor>,
4058    cx: &mut WindowContext<'_>,
4059) -> impl IntoElement {
4060    let commands = SlashCommandRegistry::global(cx);
4061
4062    slash_command_picker::SlashCommandSelector::new(
4063        commands.clone(),
4064        active_context_editor,
4065        IconButton::new("trigger", IconName::SlashSquare)
4066            .icon_size(IconSize::Small)
4067            .tooltip(|cx| {
4068                Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
4069            }),
4070    )
4071}
4072
4073impl ContextEditorToolbarItem {
4074    pub fn new(
4075        workspace: &Workspace,
4076        model_selector_menu_handle: PopoverMenuHandle<Picker<ModelPickerDelegate>>,
4077        model_summary_editor: View<Editor>,
4078    ) -> Self {
4079        Self {
4080            fs: workspace.app_state().fs.clone(),
4081            workspace: workspace.weak_handle(),
4082            active_context_editor: None,
4083            model_summary_editor,
4084            model_selector_menu_handle,
4085        }
4086    }
4087
4088    fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
4089        let context = &self
4090            .active_context_editor
4091            .as_ref()?
4092            .upgrade()?
4093            .read(cx)
4094            .context;
4095        let (token_count_color, token_count, max_token_count) = match token_state(context, cx)? {
4096            TokenState::NoTokensLeft {
4097                max_token_count,
4098                token_count,
4099            } => (Color::Error, token_count, max_token_count),
4100            TokenState::HasMoreTokens {
4101                max_token_count,
4102                token_count,
4103                over_warn_threshold,
4104            } => {
4105                let color = if over_warn_threshold {
4106                    Color::Warning
4107                } else {
4108                    Color::Muted
4109                };
4110                (color, token_count, max_token_count)
4111            }
4112        };
4113        Some(
4114            h_flex()
4115                .gap_0p5()
4116                .child(
4117                    Label::new(humanize_token_count(token_count))
4118                        .size(LabelSize::Small)
4119                        .color(token_count_color),
4120                )
4121                .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
4122                .child(
4123                    Label::new(humanize_token_count(max_token_count))
4124                        .size(LabelSize::Small)
4125                        .color(Color::Muted),
4126                ),
4127        )
4128    }
4129}
4130
4131impl Render for ContextEditorToolbarItem {
4132    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4133        let left_side = h_flex()
4134            .pl_1()
4135            .gap_2()
4136            .flex_1()
4137            .min_w(rems(DEFAULT_TAB_TITLE.len() as f32))
4138            .when(self.active_context_editor.is_some(), |left_side| {
4139                left_side.child(self.model_summary_editor.clone())
4140            });
4141        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
4142        let active_model = LanguageModelRegistry::read_global(cx).active_model();
4143        let weak_self = cx.view().downgrade();
4144        let right_side = h_flex()
4145            .gap_2()
4146            .child(
4147                ModelSelector::new(
4148                    self.fs.clone(),
4149                    ButtonLike::new("active-model")
4150                        .style(ButtonStyle::Subtle)
4151                        .child(
4152                            h_flex()
4153                                .w_full()
4154                                .gap_0p5()
4155                                .child(
4156                                    div()
4157                                        .overflow_x_hidden()
4158                                        .flex_grow()
4159                                        .whitespace_nowrap()
4160                                        .child(match (active_provider, active_model) {
4161                                            (Some(provider), Some(model)) => h_flex()
4162                                                .gap_1()
4163                                                .child(
4164                                                    Icon::new(model.icon().unwrap_or_else(|| provider.icon()))
4165                                                        .color(Color::Muted)
4166                                                        .size(IconSize::XSmall),
4167                                                )
4168                                                .child(
4169                                                    Label::new(model.name().0)
4170                                                        .size(LabelSize::Small)
4171                                                        .color(Color::Muted),
4172                                                )
4173                                                .into_any_element(),
4174                                            _ => Label::new("No model selected")
4175                                                .size(LabelSize::Small)
4176                                                .color(Color::Muted)
4177                                                .into_any_element(),
4178                                        }),
4179                                )
4180                                .child(
4181                                    Icon::new(IconName::ChevronDown)
4182                                        .color(Color::Muted)
4183                                        .size(IconSize::XSmall),
4184                                ),
4185                        )
4186                        .tooltip(move |cx| {
4187                            Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
4188                        }),
4189                )
4190                .with_handle(self.model_selector_menu_handle.clone()),
4191            )
4192            .children(self.render_remaining_tokens(cx))
4193            .child(
4194                PopoverMenu::new("context-editor-popover")
4195                    .trigger(
4196                        IconButton::new("context-editor-trigger", IconName::EllipsisVertical)
4197                            .icon_size(IconSize::Small)
4198                            .tooltip(|cx| Tooltip::text("Open Context Options", cx)),
4199                    )
4200                    .menu({
4201                        let weak_self = weak_self.clone();
4202                        move |cx| {
4203                            let weak_self = weak_self.clone();
4204                            Some(ContextMenu::build(cx, move |menu, cx| {
4205                                let context = weak_self
4206                                    .update(cx, |this, cx| {
4207                                        active_editor_focus_handle(&this.workspace, cx)
4208                                    })
4209                                    .ok()
4210                                    .flatten();
4211                                menu.when_some(context, |menu, context| menu.context(context))
4212                                    .entry("Regenerate Context Title", None, {
4213                                        let weak_self = weak_self.clone();
4214                                        move |cx| {
4215                                            weak_self
4216                                                .update(cx, |_, cx| {
4217                                                    cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
4218                                                })
4219                                                .ok();
4220                                        }
4221                                    })
4222                                    .custom_entry(
4223                                        |_| {
4224                                            h_flex()
4225                                                .w_full()
4226                                                .justify_between()
4227                                                .gap_2()
4228                                                .child(Label::new("Insert Context"))
4229                                                .child(Label::new("/ command").color(Color::Muted))
4230                                                .into_any()
4231                                        },
4232                                        {
4233                                            let weak_self = weak_self.clone();
4234                                            move |cx| {
4235                                                weak_self
4236                                                    .update(cx, |this, cx| {
4237                                                        if let Some(editor) =
4238                                                        &this.active_context_editor
4239                                                        {
4240                                                            editor
4241                                                                .update(cx, |this, cx| {
4242                                                                    this.slash_menu_handle
4243                                                                        .toggle(cx);
4244                                                                })
4245                                                                .ok();
4246                                                        }
4247                                                    })
4248                                                    .ok();
4249                                            }
4250                                        },
4251                                    )
4252                                    .action("Insert Selection", QuoteSelection.boxed_clone())
4253                            }))
4254                        }
4255                    }),
4256            );
4257
4258        h_flex()
4259            .size_full()
4260            .gap_2()
4261            .justify_between()
4262            .child(left_side)
4263            .child(right_side)
4264    }
4265}
4266
4267impl ToolbarItemView for ContextEditorToolbarItem {
4268    fn set_active_pane_item(
4269        &mut self,
4270        active_pane_item: Option<&dyn ItemHandle>,
4271        cx: &mut ViewContext<Self>,
4272    ) -> ToolbarItemLocation {
4273        self.active_context_editor = active_pane_item
4274            .and_then(|item| item.act_as::<ContextEditor>(cx))
4275            .map(|editor| editor.downgrade());
4276        cx.notify();
4277        if self.active_context_editor.is_none() {
4278            ToolbarItemLocation::Hidden
4279        } else {
4280            ToolbarItemLocation::PrimaryRight
4281        }
4282    }
4283
4284    fn pane_focus_update(&mut self, _pane_focused: bool, cx: &mut ViewContext<Self>) {
4285        cx.notify();
4286    }
4287}
4288
4289impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
4290
4291enum ContextEditorToolbarItemEvent {
4292    RegenerateSummary,
4293}
4294impl EventEmitter<ContextEditorToolbarItemEvent> for ContextEditorToolbarItem {}
4295
4296pub struct ContextHistory {
4297    picker: View<Picker<SavedContextPickerDelegate>>,
4298    _subscriptions: Vec<Subscription>,
4299    assistant_panel: WeakView<AssistantPanel>,
4300}
4301
4302impl ContextHistory {
4303    fn new(
4304        project: Model<Project>,
4305        context_store: Model<ContextStore>,
4306        assistant_panel: WeakView<AssistantPanel>,
4307        cx: &mut ViewContext<Self>,
4308    ) -> Self {
4309        let picker = cx.new_view(|cx| {
4310            Picker::uniform_list(
4311                SavedContextPickerDelegate::new(project, context_store.clone()),
4312                cx,
4313            )
4314            .modal(false)
4315            .max_height(None)
4316        });
4317
4318        let _subscriptions = vec![
4319            cx.observe(&context_store, |this, _, cx| {
4320                this.picker.update(cx, |picker, cx| picker.refresh(cx));
4321            }),
4322            cx.subscribe(&picker, Self::handle_picker_event),
4323        ];
4324
4325        Self {
4326            picker,
4327            _subscriptions,
4328            assistant_panel,
4329        }
4330    }
4331
4332    fn handle_picker_event(
4333        &mut self,
4334        _: View<Picker<SavedContextPickerDelegate>>,
4335        event: &SavedContextPickerEvent,
4336        cx: &mut ViewContext<Self>,
4337    ) {
4338        let SavedContextPickerEvent::Confirmed(context) = event;
4339        self.assistant_panel
4340            .update(cx, |assistant_panel, cx| match context {
4341                ContextMetadata::Remote(metadata) => {
4342                    assistant_panel
4343                        .open_remote_context(metadata.id.clone(), cx)
4344                        .detach_and_log_err(cx);
4345                }
4346                ContextMetadata::Saved(metadata) => {
4347                    assistant_panel
4348                        .open_saved_context(metadata.path.clone(), cx)
4349                        .detach_and_log_err(cx);
4350                }
4351            })
4352            .ok();
4353    }
4354}
4355
4356impl Render for ContextHistory {
4357    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
4358        div().size_full().child(self.picker.clone())
4359    }
4360}
4361
4362impl FocusableView for ContextHistory {
4363    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
4364        self.picker.focus_handle(cx)
4365    }
4366}
4367
4368impl EventEmitter<()> for ContextHistory {}
4369
4370impl Item for ContextHistory {
4371    type Event = ();
4372
4373    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
4374        Some("History".into())
4375    }
4376}
4377
4378pub struct ConfigurationView {
4379    focus_handle: FocusHandle,
4380    configuration_views: HashMap<LanguageModelProviderId, AnyView>,
4381    _registry_subscription: Subscription,
4382}
4383
4384impl ConfigurationView {
4385    fn new(cx: &mut ViewContext<Self>) -> Self {
4386        let focus_handle = cx.focus_handle();
4387
4388        let registry_subscription = cx.subscribe(
4389            &LanguageModelRegistry::global(cx),
4390            |this, _, event: &language_model::Event, cx| match event {
4391                language_model::Event::AddedProvider(provider_id) => {
4392                    let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
4393                    if let Some(provider) = provider {
4394                        this.add_configuration_view(&provider, cx);
4395                    }
4396                }
4397                language_model::Event::RemovedProvider(provider_id) => {
4398                    this.remove_configuration_view(provider_id);
4399                }
4400                _ => {}
4401            },
4402        );
4403
4404        let mut this = Self {
4405            focus_handle,
4406            configuration_views: HashMap::default(),
4407            _registry_subscription: registry_subscription,
4408        };
4409        this.build_configuration_views(cx);
4410        this
4411    }
4412
4413    fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
4414        let providers = LanguageModelRegistry::read_global(cx).providers();
4415        for provider in providers {
4416            self.add_configuration_view(&provider, cx);
4417        }
4418    }
4419
4420    fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
4421        self.configuration_views.remove(provider_id);
4422    }
4423
4424    fn add_configuration_view(
4425        &mut self,
4426        provider: &Arc<dyn LanguageModelProvider>,
4427        cx: &mut ViewContext<Self>,
4428    ) {
4429        let configuration_view = provider.configuration_view(cx);
4430        self.configuration_views
4431            .insert(provider.id(), configuration_view);
4432    }
4433
4434    fn render_provider_view(
4435        &mut self,
4436        provider: &Arc<dyn LanguageModelProvider>,
4437        cx: &mut ViewContext<Self>,
4438    ) -> Div {
4439        let provider_id = provider.id().0.clone();
4440        let provider_name = provider.name().0.clone();
4441        let configuration_view = self.configuration_views.get(&provider.id()).cloned();
4442
4443        let open_new_context = cx.listener({
4444            let provider = provider.clone();
4445            move |_, _, cx| {
4446                cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
4447                    provider.clone(),
4448                ))
4449            }
4450        });
4451
4452        v_flex()
4453            .gap_2()
4454            .child(
4455                h_flex()
4456                    .justify_between()
4457                    .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
4458                    .when(provider.is_authenticated(cx), move |this| {
4459                        this.child(
4460                            h_flex().justify_end().child(
4461                                Button::new(
4462                                    SharedString::from(format!("new-context-{provider_id}")),
4463                                    "Open new context",
4464                                )
4465                                .icon_position(IconPosition::Start)
4466                                .icon(IconName::Plus)
4467                                .style(ButtonStyle::Filled)
4468                                .layer(ElevationIndex::ModalSurface)
4469                                .on_click(open_new_context),
4470                            ),
4471                        )
4472                    }),
4473            )
4474            .child(
4475                div()
4476                    .p(Spacing::Large.rems(cx))
4477                    .bg(cx.theme().colors().surface_background)
4478                    .border_1()
4479                    .border_color(cx.theme().colors().border_variant)
4480                    .rounded_md()
4481                    .when(configuration_view.is_none(), |this| {
4482                        this.child(div().child(Label::new(format!(
4483                            "No configuration view for {}",
4484                            provider_name
4485                        ))))
4486                    })
4487                    .when_some(configuration_view, |this, configuration_view| {
4488                        this.child(configuration_view)
4489                    }),
4490            )
4491    }
4492}
4493
4494impl Render for ConfigurationView {
4495    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
4496        let providers = LanguageModelRegistry::read_global(cx).providers();
4497        let provider_views = providers
4498            .into_iter()
4499            .map(|provider| self.render_provider_view(&provider, cx))
4500            .collect::<Vec<_>>();
4501
4502        let mut element = v_flex()
4503            .id("assistant-configuration-view")
4504            .track_focus(&self.focus_handle)
4505            .bg(cx.theme().colors().editor_background)
4506            .size_full()
4507            .overflow_y_scroll()
4508            .child(
4509                v_flex()
4510                    .p(Spacing::XXLarge.rems(cx))
4511                    .border_b_1()
4512                    .border_color(cx.theme().colors().border)
4513                    .gap_1()
4514                    .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
4515                    .child(
4516                        Label::new(
4517                            "At least one LLM provider must be configured to use the Assistant.",
4518                        )
4519                        .color(Color::Muted),
4520                    ),
4521            )
4522            .child(
4523                v_flex()
4524                    .p(Spacing::XXLarge.rems(cx))
4525                    .mt_1()
4526                    .gap_6()
4527                    .flex_1()
4528                    .children(provider_views),
4529            )
4530            .into_any();
4531
4532        // We use a canvas here to get scrolling to work in the ConfigurationView. It's a workaround
4533        // because we couldn't the element to take up the size of the parent.
4534        canvas(
4535            move |bounds, cx| {
4536                element.prepaint_as_root(bounds.origin, bounds.size.into(), cx);
4537                element
4538            },
4539            |_, mut element, cx| {
4540                element.paint(cx);
4541            },
4542        )
4543        .flex_1()
4544        .w_full()
4545    }
4546}
4547
4548pub enum ConfigurationViewEvent {
4549    NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
4550}
4551
4552impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
4553
4554impl FocusableView for ConfigurationView {
4555    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
4556        self.focus_handle.clone()
4557    }
4558}
4559
4560impl Item for ConfigurationView {
4561    type Event = ConfigurationViewEvent;
4562
4563    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
4564        Some("Configuration".into())
4565    }
4566}
4567
4568type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
4569
4570fn render_slash_command_output_toggle(
4571    row: MultiBufferRow,
4572    is_folded: bool,
4573    fold: ToggleFold,
4574    _cx: &mut WindowContext,
4575) -> AnyElement {
4576    Disclosure::new(
4577        ("slash-command-output-fold-indicator", row.0 as u64),
4578        !is_folded,
4579    )
4580    .selected(is_folded)
4581    .on_click(move |_e, cx| fold(!is_folded, cx))
4582    .into_any_element()
4583}
4584
4585fn quote_selection_fold_placeholder(title: String, editor: WeakView<Editor>) -> FoldPlaceholder {
4586    FoldPlaceholder {
4587        render: Arc::new({
4588            move |fold_id, fold_range, _cx| {
4589                let editor = editor.clone();
4590                ButtonLike::new(fold_id)
4591                    .style(ButtonStyle::Filled)
4592                    .layer(ElevationIndex::ElevatedSurface)
4593                    .child(Icon::new(IconName::TextSelect))
4594                    .child(Label::new(title.clone()).single_line())
4595                    .on_click(move |_, cx| {
4596                        editor
4597                            .update(cx, |editor, cx| {
4598                                let buffer_start = fold_range
4599                                    .start
4600                                    .to_point(&editor.buffer().read(cx).read(cx));
4601                                let buffer_row = MultiBufferRow(buffer_start.row);
4602                                editor.unfold_at(&UnfoldAt { buffer_row }, cx);
4603                            })
4604                            .ok();
4605                    })
4606                    .into_any_element()
4607            }
4608        }),
4609        constrain_width: false,
4610        merge_adjacent: false,
4611    }
4612}
4613
4614fn render_quote_selection_output_toggle(
4615    row: MultiBufferRow,
4616    is_folded: bool,
4617    fold: ToggleFold,
4618    _cx: &mut WindowContext,
4619) -> AnyElement {
4620    Disclosure::new(("quote-selection-indicator", row.0 as u64), !is_folded)
4621        .selected(is_folded)
4622        .on_click(move |_e, cx| fold(!is_folded, cx))
4623        .into_any_element()
4624}
4625
4626fn render_pending_slash_command_gutter_decoration(
4627    row: MultiBufferRow,
4628    status: &PendingSlashCommandStatus,
4629    confirm_command: Arc<dyn Fn(&mut WindowContext)>,
4630) -> AnyElement {
4631    let mut icon = IconButton::new(
4632        ("slash-command-gutter-decoration", row.0),
4633        ui::IconName::TriangleRight,
4634    )
4635    .on_click(move |_e, cx| confirm_command(cx))
4636    .icon_size(ui::IconSize::Small)
4637    .size(ui::ButtonSize::None);
4638
4639    match status {
4640        PendingSlashCommandStatus::Idle => {
4641            icon = icon.icon_color(Color::Muted);
4642        }
4643        PendingSlashCommandStatus::Running { .. } => {
4644            icon = icon.selected(true);
4645        }
4646        PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
4647    }
4648
4649    icon.into_any_element()
4650}
4651
4652fn render_docs_slash_command_trailer(
4653    row: MultiBufferRow,
4654    command: PendingSlashCommand,
4655    cx: &mut WindowContext,
4656) -> AnyElement {
4657    if command.arguments.is_empty() {
4658        return Empty.into_any();
4659    }
4660    let args = DocsSlashCommandArgs::parse(&command.arguments);
4661
4662    let Some(store) = args
4663        .provider()
4664        .and_then(|provider| IndexedDocsStore::try_global(provider, cx).ok())
4665    else {
4666        return Empty.into_any();
4667    };
4668
4669    let Some(package) = args.package() else {
4670        return Empty.into_any();
4671    };
4672
4673    let mut children = Vec::new();
4674
4675    if store.is_indexing(&package) {
4676        children.push(
4677            div()
4678                .id(("crates-being-indexed", row.0))
4679                .child(Icon::new(IconName::ArrowCircle).with_animation(
4680                    "arrow-circle",
4681                    Animation::new(Duration::from_secs(4)).repeat(),
4682                    |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
4683                ))
4684                .tooltip({
4685                    let package = package.clone();
4686                    move |cx| Tooltip::text(format!("Indexing {package}"), cx)
4687                })
4688                .into_any_element(),
4689        );
4690    }
4691
4692    if let Some(latest_error) = store.latest_error_for_package(&package) {
4693        children.push(
4694            div()
4695                .id(("latest-error", row.0))
4696                .child(
4697                    Icon::new(IconName::ExclamationTriangle)
4698                        .size(IconSize::Small)
4699                        .color(Color::Warning),
4700                )
4701                .tooltip(move |cx| Tooltip::text(format!("Failed to index: {latest_error}"), cx))
4702                .into_any_element(),
4703        )
4704    }
4705
4706    let is_indexing = store.is_indexing(&package);
4707    let latest_error = store.latest_error_for_package(&package);
4708
4709    if !is_indexing && latest_error.is_none() {
4710        return Empty.into_any();
4711    }
4712
4713    h_flex().gap_2().children(children).into_any_element()
4714}
4715
4716fn make_lsp_adapter_delegate(
4717    project: &Model<Project>,
4718    cx: &mut AppContext,
4719) -> Result<Arc<dyn LspAdapterDelegate>> {
4720    project.update(cx, |project, cx| {
4721        // TODO: Find the right worktree.
4722        let worktree = project
4723            .worktrees(cx)
4724            .next()
4725            .ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
4726        Ok(ProjectLspAdapterDelegate::new(project, &worktree, cx) as Arc<dyn LspAdapterDelegate>)
4727    })
4728}
4729
4730fn slash_command_error_block_renderer(message: String) -> RenderBlock {
4731    Box::new(move |_| {
4732        div()
4733            .pl_6()
4734            .child(
4735                Label::new(format!("error: {}", message))
4736                    .single_line()
4737                    .color(Color::Error),
4738            )
4739            .into_any()
4740    })
4741}
4742
4743enum TokenState {
4744    NoTokensLeft {
4745        max_token_count: usize,
4746        token_count: usize,
4747    },
4748    HasMoreTokens {
4749        max_token_count: usize,
4750        token_count: usize,
4751        over_warn_threshold: bool,
4752    },
4753}
4754
4755fn token_state(context: &Model<Context>, cx: &AppContext) -> Option<TokenState> {
4756    const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
4757
4758    let model = LanguageModelRegistry::read_global(cx).active_model()?;
4759    let token_count = context.read(cx).token_count()?;
4760    let max_token_count = model.max_token_count();
4761
4762    let remaining_tokens = max_token_count as isize - token_count as isize;
4763    let token_state = if remaining_tokens <= 0 {
4764        TokenState::NoTokensLeft {
4765            max_token_count,
4766            token_count,
4767        }
4768    } else {
4769        let over_warn_threshold =
4770            token_count as f32 / max_token_count as f32 >= WARNING_TOKEN_THRESHOLD;
4771        TokenState::HasMoreTokens {
4772            max_token_count,
4773            token_count,
4774            over_warn_threshold,
4775        }
4776    };
4777    Some(token_state)
4778}
4779
4780fn size_for_image(data: &RenderImage, max_size: Size<Pixels>) -> Size<Pixels> {
4781    let image_size = data
4782        .size(0)
4783        .map(|dimension| Pixels::from(u32::from(dimension)));
4784    let image_ratio = image_size.width / image_size.height;
4785    let bounds_ratio = max_size.width / max_size.height;
4786
4787    if image_size.width > max_size.width || image_size.height > max_size.height {
4788        if bounds_ratio > image_ratio {
4789            size(
4790                image_size.width * (max_size.height / image_size.height),
4791                max_size.height,
4792            )
4793        } else {
4794            size(
4795                max_size.width,
4796                image_size.height * (max_size.width / image_size.width),
4797            )
4798        }
4799    } else {
4800        size(image_size.width, image_size.height)
4801    }
4802}
4803
4804enum ConfigurationError {
4805    NoProvider,
4806    ProviderNotAuthenticated,
4807}
4808
4809fn configuration_error(cx: &AppContext) -> Option<ConfigurationError> {
4810    let provider = LanguageModelRegistry::read_global(cx).active_provider();
4811    let is_authenticated = provider
4812        .as_ref()
4813        .map_or(false, |provider| provider.is_authenticated(cx));
4814
4815    if provider.is_some() && is_authenticated {
4816        return None;
4817    }
4818
4819    if provider.is_none() {
4820        return Some(ConfigurationError::NoProvider);
4821    }
4822
4823    if !is_authenticated {
4824        return Some(ConfigurationError::ProviderNotAuthenticated);
4825    }
4826
4827    None
4828}