assistant_panel.rs

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