assistant_panel.rs

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