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