assistant_panel.rs

   1use crate::{
   2    assistant_settings::{AssistantDockPosition, AssistantSettings},
   3    humanize_token_count,
   4    prompt_library::open_prompt_library,
   5    prompts::PromptBuilder,
   6    slash_command::{
   7        default_command::DefaultSlashCommand,
   8        docs_command::{DocsSlashCommand, DocsSlashCommandArgs},
   9        SlashCommandCompletionProvider, SlashCommandRegistry,
  10    },
  11    terminal_inline_assistant::TerminalInlineAssistant,
  12    Assist, CodegenStatus, ConfirmCommand, Context, ContextEvent, ContextId, ContextStore,
  13    CycleMessageRole, DebugEditSteps, DeployHistory, DeployPromptLibrary, InlineAssist,
  14    InlineAssistId, InlineAssistant, InsertIntoEditor, MessageStatus, ModelSelector,
  15    PendingSlashCommand, PendingSlashCommandStatus, QuoteSelection, RemoteContextMetadata,
  16    ResolvedWorkflowStep, SavedContextMetadata, Split, ToggleFocus, ToggleModelSelector,
  17    WorkflowStepStatus,
  18};
  19use crate::{ContextStoreEvent, ShowConfiguration};
  20use anyhow::{anyhow, Context as _, Result};
  21use assistant_slash_command::{SlashCommand, SlashCommandOutputSection};
  22use client::{proto, Client, Status};
  23use collections::{BTreeSet, HashMap, HashSet};
  24use editor::{
  25    actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
  26    display_map::{
  27        BlockDisposition, BlockProperties, BlockStyle, Crease, CustomBlockId, RenderBlock,
  28        ToDisplayPoint,
  29    },
  30    scroll::{Autoscroll, AutoscrollStrategy, ScrollAnchor},
  31    Anchor, Editor, EditorEvent, ExcerptRange, MultiBuffer, RowExt, ToOffset as _, ToPoint,
  32};
  33use editor::{display_map::CreaseId, FoldPlaceholder};
  34use fs::Fs;
  35use gpui::{
  36    div, percentage, point, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext,
  37    AsyncWindowContext, ClipboardItem, Context as _, DismissEvent, Empty, Entity, EventEmitter,
  38    FocusHandle, FocusableView, FontWeight, InteractiveElement, IntoElement, Model, ParentElement,
  39    Pixels, ReadGlobal, Render, SharedString, StatefulInteractiveElement, Styled, Subscription,
  40    Task, Transformation, UpdateGlobal, View, ViewContext, VisualContext, WeakView, WindowContext,
  41};
  42use indexed_docs::IndexedDocsStore;
  43use language::{
  44    language_settings::SoftWrap, Capability, LanguageRegistry, LspAdapterDelegate, Point, ToOffset,
  45};
  46use language_model::{
  47    provider::cloud::PROVIDER_ID, LanguageModelProvider, LanguageModelProviderId,
  48    LanguageModelRegistry, Role,
  49};
  50use multi_buffer::MultiBufferRow;
  51use picker::{Picker, PickerDelegate};
  52use project::{Project, ProjectLspAdapterDelegate};
  53use search::{buffer_search::DivRegistrar, BufferSearchBar};
  54use settings::{update_settings_file, Settings};
  55use smol::stream::StreamExt;
  56use std::{
  57    borrow::Cow,
  58    cmp::{self, Ordering},
  59    fmt::Write,
  60    ops::Range,
  61    path::PathBuf,
  62    sync::Arc,
  63    time::Duration,
  64};
  65use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
  66use ui::TintColor;
  67use ui::{
  68    prelude::*,
  69    utils::{format_distance_from_now, DateTimeType},
  70    Avatar, AvatarShape, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
  71    ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tooltip,
  72};
  73use util::ResultExt;
  74use workspace::{
  75    dock::{DockPosition, Panel, PanelEvent},
  76    item::{self, FollowableItem, Item, ItemHandle},
  77    notifications::NotifyTaskExt,
  78    pane::{self, SaveIntent},
  79    searchable::{SearchEvent, SearchableItem},
  80    Pane, Save, ToggleZoom, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
  81};
  82use workspace::{searchable::SearchableItemHandle, NewFile};
  83
  84pub fn init(cx: &mut AppContext) {
  85    workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
  86    cx.observe_new_views(
  87        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
  88            workspace
  89                .register_action(|workspace, _: &ToggleFocus, cx| {
  90                    let settings = AssistantSettings::get_global(cx);
  91                    if !settings.enabled {
  92                        return;
  93                    }
  94
  95                    workspace.toggle_panel_focus::<AssistantPanel>(cx);
  96                })
  97                .register_action(AssistantPanel::inline_assist)
  98                .register_action(ContextEditor::quote_selection)
  99                .register_action(ContextEditor::insert_selection)
 100                .register_action(AssistantPanel::show_configuration);
 101        },
 102    )
 103    .detach();
 104
 105    cx.observe_new_views(
 106        |terminal_panel: &mut TerminalPanel, cx: &mut ViewContext<TerminalPanel>| {
 107            let settings = AssistantSettings::get_global(cx);
 108            if !settings.enabled {
 109                return;
 110            }
 111
 112            terminal_panel.register_tab_bar_button(cx.new_view(|_| InlineAssistTabBarButton), cx);
 113        },
 114    )
 115    .detach();
 116}
 117
 118struct InlineAssistTabBarButton;
 119
 120impl Render for InlineAssistTabBarButton {
 121    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 122        IconButton::new("terminal_inline_assistant", IconName::MagicWand)
 123            .icon_size(IconSize::Small)
 124            .on_click(cx.listener(|_, _, cx| {
 125                cx.dispatch_action(InlineAssist::default().boxed_clone());
 126            }))
 127            .tooltip(move |cx| Tooltip::for_action("Inline Assist", &InlineAssist::default(), cx))
 128    }
 129}
 130
 131pub enum AssistantPanelEvent {
 132    ContextEdited,
 133}
 134
 135pub struct AssistantPanel {
 136    pane: View<Pane>,
 137    workspace: WeakView<Workspace>,
 138    width: Option<Pixels>,
 139    height: Option<Pixels>,
 140    project: Model<Project>,
 141    context_store: Model<ContextStore>,
 142    languages: Arc<LanguageRegistry>,
 143    fs: Arc<dyn Fs>,
 144    subscriptions: Vec<Subscription>,
 145    model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
 146    model_summary_editor: View<Editor>,
 147    authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
 148    configuration_subscription: Option<Subscription>,
 149    client_status: Option<client::Status>,
 150    watch_client_status: Option<Task<()>>,
 151    show_zed_ai_notice: bool,
 152}
 153
 154#[derive(Clone)]
 155enum ContextMetadata {
 156    Remote(RemoteContextMetadata),
 157    Saved(SavedContextMetadata),
 158}
 159
 160struct SavedContextPickerDelegate {
 161    store: Model<ContextStore>,
 162    project: Model<Project>,
 163    matches: Vec<ContextMetadata>,
 164    selected_index: usize,
 165}
 166
 167enum SavedContextPickerEvent {
 168    Confirmed(ContextMetadata),
 169}
 170
 171enum InlineAssistTarget {
 172    Editor(View<Editor>, bool),
 173    Terminal(View<TerminalView>),
 174}
 175
 176impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
 177
 178impl SavedContextPickerDelegate {
 179    fn new(project: Model<Project>, store: Model<ContextStore>) -> Self {
 180        Self {
 181            project,
 182            store,
 183            matches: Vec::new(),
 184            selected_index: 0,
 185        }
 186    }
 187}
 188
 189impl PickerDelegate for SavedContextPickerDelegate {
 190    type ListItem = ListItem;
 191
 192    fn match_count(&self) -> usize {
 193        self.matches.len()
 194    }
 195
 196    fn selected_index(&self) -> usize {
 197        self.selected_index
 198    }
 199
 200    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
 201        self.selected_index = ix;
 202    }
 203
 204    fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
 205        "Search...".into()
 206    }
 207
 208    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
 209        let search = self.store.read(cx).search(query, cx);
 210        cx.spawn(|this, mut cx| async move {
 211            let matches = search.await;
 212            this.update(&mut cx, |this, cx| {
 213                let host_contexts = this.delegate.store.read(cx).host_contexts();
 214                this.delegate.matches = host_contexts
 215                    .iter()
 216                    .cloned()
 217                    .map(ContextMetadata::Remote)
 218                    .chain(matches.into_iter().map(ContextMetadata::Saved))
 219                    .collect();
 220                this.delegate.selected_index = 0;
 221                cx.notify();
 222            })
 223            .ok();
 224        })
 225    }
 226
 227    fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
 228        if let Some(metadata) = self.matches.get(self.selected_index) {
 229            cx.emit(SavedContextPickerEvent::Confirmed(metadata.clone()));
 230        }
 231    }
 232
 233    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
 234
 235    fn render_match(
 236        &self,
 237        ix: usize,
 238        selected: bool,
 239        cx: &mut ViewContext<Picker<Self>>,
 240    ) -> Option<Self::ListItem> {
 241        let context = self.matches.get(ix)?;
 242        let item = match context {
 243            ContextMetadata::Remote(context) => {
 244                let host_user = self.project.read(cx).host().and_then(|collaborator| {
 245                    self.project
 246                        .read(cx)
 247                        .user_store()
 248                        .read(cx)
 249                        .get_cached_user(collaborator.user_id)
 250                });
 251                div()
 252                    .flex()
 253                    .w_full()
 254                    .justify_between()
 255                    .gap_2()
 256                    .child(
 257                        h_flex().flex_1().overflow_x_hidden().child(
 258                            Label::new(context.summary.clone().unwrap_or(DEFAULT_TAB_TITLE.into()))
 259                                .size(LabelSize::Small),
 260                        ),
 261                    )
 262                    .child(
 263                        h_flex()
 264                            .gap_2()
 265                            .children(if let Some(host_user) = host_user {
 266                                vec![
 267                                    Avatar::new(host_user.avatar_uri.clone())
 268                                        .shape(AvatarShape::Circle)
 269                                        .into_any_element(),
 270                                    Label::new(format!("Shared by @{}", host_user.github_login))
 271                                        .color(Color::Muted)
 272                                        .size(LabelSize::Small)
 273                                        .into_any_element(),
 274                                ]
 275                            } else {
 276                                vec![Label::new("Shared by host")
 277                                    .color(Color::Muted)
 278                                    .size(LabelSize::Small)
 279                                    .into_any_element()]
 280                            }),
 281                    )
 282            }
 283            ContextMetadata::Saved(context) => div()
 284                .flex()
 285                .w_full()
 286                .justify_between()
 287                .gap_2()
 288                .child(
 289                    h_flex()
 290                        .flex_1()
 291                        .child(Label::new(context.title.clone()).size(LabelSize::Small))
 292                        .overflow_x_hidden(),
 293                )
 294                .child(
 295                    Label::new(format_distance_from_now(
 296                        DateTimeType::Local(context.mtime),
 297                        false,
 298                        true,
 299                        true,
 300                    ))
 301                    .color(Color::Muted)
 302                    .size(LabelSize::Small),
 303                ),
 304        };
 305        Some(
 306            ListItem::new(ix)
 307                .inset(true)
 308                .spacing(ListItemSpacing::Sparse)
 309                .selected(selected)
 310                .child(item),
 311        )
 312    }
 313}
 314
 315impl AssistantPanel {
 316    pub fn load(
 317        workspace: WeakView<Workspace>,
 318        prompt_builder: Arc<PromptBuilder>,
 319        cx: AsyncWindowContext,
 320    ) -> Task<Result<View<Self>>> {
 321        cx.spawn(|mut cx| async move {
 322            let context_store = workspace
 323                .update(&mut cx, |workspace, cx| {
 324                    let project = workspace.project().clone();
 325                    ContextStore::new(project, prompt_builder.clone(), cx)
 326                })?
 327                .await?;
 328
 329            workspace.update(&mut cx, |workspace, cx| {
 330                // TODO: deserialize state.
 331                cx.new_view(|cx| Self::new(workspace, context_store, cx))
 332            })
 333        })
 334    }
 335
 336    fn new(
 337        workspace: &Workspace,
 338        context_store: Model<ContextStore>,
 339        cx: &mut ViewContext<Self>,
 340    ) -> Self {
 341        let model_selector_menu_handle = PopoverMenuHandle::default();
 342        let model_summary_editor = cx.new_view(|cx| Editor::single_line(cx));
 343        let context_editor_toolbar = cx.new_view(|_| {
 344            ContextEditorToolbarItem::new(
 345                workspace,
 346                model_selector_menu_handle.clone(),
 347                model_summary_editor.clone(),
 348            )
 349        });
 350        let pane = cx.new_view(|cx| {
 351            let mut pane = Pane::new(
 352                workspace.weak_handle(),
 353                workspace.project().clone(),
 354                Default::default(),
 355                None,
 356                NewFile.boxed_clone(),
 357                cx,
 358            );
 359            pane.set_can_split(false, cx);
 360            pane.set_can_navigate(true, cx);
 361            pane.display_nav_history_buttons(None);
 362            pane.set_should_display_tab_bar(|_| true);
 363            pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
 364                let focus_handle = pane.focus_handle(cx);
 365                let left_children = IconButton::new("history", IconName::TextSearch)
 366                    .on_click(
 367                        cx.listener(|_, _, cx| cx.dispatch_action(DeployHistory.boxed_clone())),
 368                    )
 369                    .tooltip(move |cx| {
 370                        cx.new_view(|cx| {
 371                            let keybind =
 372                                KeyBinding::for_action_in(&DeployHistory, &focus_handle, cx);
 373                            Tooltip::new("History").key_binding(keybind)
 374                        })
 375                        .into()
 376                    })
 377                    .selected(
 378                        pane.active_item()
 379                            .map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
 380                    );
 381                let right_children = h_flex()
 382                    .gap(Spacing::Small.rems(cx))
 383                    .child(
 384                        IconButton::new("new-context", IconName::Plus)
 385                            .on_click(
 386                                cx.listener(|_, _, cx| cx.dispatch_action(NewFile.boxed_clone())),
 387                            )
 388                            .tooltip(|cx| Tooltip::for_action("New Context", &NewFile, cx)),
 389                    )
 390                    .child(
 391                        IconButton::new("menu", IconName::Menu)
 392                            .icon_size(IconSize::Small)
 393                            .on_click(cx.listener(|pane, _, cx| {
 394                                let zoom_label = if pane.is_zoomed() {
 395                                    "Zoom Out"
 396                                } else {
 397                                    "Zoom In"
 398                                };
 399                                let menu = ContextMenu::build(cx, |menu, cx| {
 400                                    menu.context(pane.focus_handle(cx))
 401                                        .action("New Context", Box::new(NewFile))
 402                                        .action("History", Box::new(DeployHistory))
 403                                        .action("Prompt Library", Box::new(DeployPromptLibrary))
 404                                        .action("Configure", Box::new(ShowConfiguration))
 405                                        .action(zoom_label, Box::new(ToggleZoom))
 406                                });
 407                                cx.subscribe(&menu, |pane, _, _: &DismissEvent, _| {
 408                                    pane.new_item_menu = None;
 409                                })
 410                                .detach();
 411                                pane.new_item_menu = Some(menu);
 412                            })),
 413                    )
 414                    .when_some(pane.new_item_menu.as_ref(), |el, new_item_menu| {
 415                        el.child(Pane::render_menu_overlay(new_item_menu))
 416                    })
 417                    .into_any_element()
 418                    .into();
 419
 420                (Some(left_children.into_any_element()), right_children)
 421            });
 422            pane.toolbar().update(cx, |toolbar, cx| {
 423                toolbar.add_item(context_editor_toolbar.clone(), cx);
 424                toolbar.add_item(cx.new_view(BufferSearchBar::new), cx)
 425            });
 426            pane
 427        });
 428
 429        let subscriptions = vec![
 430            cx.observe(&pane, |_, _, cx| cx.notify()),
 431            cx.subscribe(&pane, Self::handle_pane_event),
 432            cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),
 433            cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),
 434            cx.subscribe(&context_store, Self::handle_context_store_event),
 435            cx.subscribe(
 436                &LanguageModelRegistry::global(cx),
 437                |this, _, event: &language_model::Event, cx| match event {
 438                    language_model::Event::ActiveModelChanged => {
 439                        this.completion_provider_changed(cx);
 440                    }
 441                    language_model::Event::ProviderStateChanged => {
 442                        this.ensure_authenticated(cx);
 443                    }
 444                    language_model::Event::AddedProvider(_)
 445                    | language_model::Event::RemovedProvider(_) => {
 446                        this.ensure_authenticated(cx);
 447                    }
 448                },
 449            ),
 450        ];
 451
 452        let watch_client_status = Self::watch_client_status(workspace.client().clone(), cx);
 453
 454        let mut this = Self {
 455            pane,
 456            workspace: workspace.weak_handle(),
 457            width: None,
 458            height: None,
 459            project: workspace.project().clone(),
 460            context_store,
 461            languages: workspace.app_state().languages.clone(),
 462            fs: workspace.app_state().fs.clone(),
 463            subscriptions,
 464            model_selector_menu_handle,
 465            model_summary_editor,
 466            authenticate_provider_task: None,
 467            configuration_subscription: None,
 468            client_status: None,
 469            watch_client_status: Some(watch_client_status),
 470            show_zed_ai_notice: false,
 471        };
 472        this.new_context(cx);
 473        this
 474    }
 475
 476    fn watch_client_status(client: Arc<Client>, cx: &mut ViewContext<Self>) -> Task<()> {
 477        let mut status_rx = client.status();
 478
 479        cx.spawn(|this, mut cx| async move {
 480            while let Some(status) = status_rx.next().await {
 481                this.update(&mut cx, |this, cx| {
 482                    if this.client_status.is_none()
 483                        || this
 484                            .client_status
 485                            .map_or(false, |old_status| old_status != status)
 486                    {
 487                        this.update_zed_ai_notice_visibility(status, cx);
 488                    }
 489                    this.client_status = Some(status);
 490                })
 491                .log_err();
 492            }
 493            this.update(&mut cx, |this, _cx| this.watch_client_status = None)
 494                .log_err();
 495        })
 496    }
 497
 498    fn handle_pane_event(
 499        &mut self,
 500        pane: View<Pane>,
 501        event: &pane::Event,
 502        cx: &mut ViewContext<Self>,
 503    ) {
 504        let update_model_summary = match event {
 505            pane::Event::Remove => {
 506                cx.emit(PanelEvent::Close);
 507                false
 508            }
 509            pane::Event::ZoomIn => {
 510                cx.emit(PanelEvent::ZoomIn);
 511                false
 512            }
 513            pane::Event::ZoomOut => {
 514                cx.emit(PanelEvent::ZoomOut);
 515                false
 516            }
 517
 518            pane::Event::AddItem { item } => {
 519                self.workspace
 520                    .update(cx, |workspace, cx| {
 521                        item.added_to_pane(workspace, self.pane.clone(), cx)
 522                    })
 523                    .ok();
 524                true
 525            }
 526
 527            pane::Event::ActivateItem { local } => {
 528                if *local {
 529                    self.workspace
 530                        .update(cx, |workspace, cx| {
 531                            workspace.unfollow_in_pane(&pane, cx);
 532                        })
 533                        .ok();
 534                }
 535                cx.emit(AssistantPanelEvent::ContextEdited);
 536                true
 537            }
 538
 539            pane::Event::RemoveItem { idx } => {
 540                if self
 541                    .pane
 542                    .read(cx)
 543                    .item_for_index(*idx)
 544                    .map_or(false, |item| item.downcast::<ConfigurationView>().is_some())
 545                {
 546                    self.configuration_subscription = None;
 547                }
 548                false
 549            }
 550            pane::Event::RemovedItem { .. } => {
 551                cx.emit(AssistantPanelEvent::ContextEdited);
 552                true
 553            }
 554
 555            _ => false,
 556        };
 557
 558        if update_model_summary {
 559            if let Some(editor) = self.active_context_editor(cx) {
 560                self.show_updated_summary(&editor, cx)
 561            }
 562        }
 563    }
 564
 565    fn handle_summary_editor_event(
 566        &mut self,
 567        model_summary_editor: View<Editor>,
 568        event: &EditorEvent,
 569        cx: &mut ViewContext<Self>,
 570    ) {
 571        if matches!(event, EditorEvent::Edited { .. }) {
 572            if let Some(context_editor) = self.active_context_editor(cx) {
 573                let new_summary = model_summary_editor.read(cx).text(cx);
 574                context_editor.update(cx, |context_editor, cx| {
 575                    context_editor.context.update(cx, |context, cx| {
 576                        if context.summary().is_none()
 577                            && (new_summary == DEFAULT_TAB_TITLE || new_summary.trim().is_empty())
 578                        {
 579                            return;
 580                        }
 581                        context.custom_summary(new_summary, cx)
 582                    });
 583                });
 584            }
 585        }
 586    }
 587
 588    fn update_zed_ai_notice_visibility(
 589        &mut self,
 590        client_status: Status,
 591        cx: &mut ViewContext<Self>,
 592    ) {
 593        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
 594
 595        // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
 596        // the provider, we want to show a nudge to sign in.
 597        let show_zed_ai_notice = client_status.is_signed_out()
 598            && active_provider.map_or(true, |provider| provider.id().0 == PROVIDER_ID);
 599
 600        self.show_zed_ai_notice = show_zed_ai_notice;
 601        cx.notify();
 602    }
 603
 604    fn handle_toolbar_event(
 605        &mut self,
 606        _: View<ContextEditorToolbarItem>,
 607        _: &ContextEditorToolbarItemEvent,
 608        cx: &mut ViewContext<Self>,
 609    ) {
 610        if let Some(context_editor) = self.active_context_editor(cx) {
 611            context_editor.update(cx, |context_editor, cx| {
 612                context_editor.context.update(cx, |context, cx| {
 613                    context.summarize(true, cx);
 614                })
 615            })
 616        }
 617    }
 618
 619    fn handle_context_store_event(
 620        &mut self,
 621        _context_store: Model<ContextStore>,
 622        event: &ContextStoreEvent,
 623        cx: &mut ViewContext<Self>,
 624    ) {
 625        let ContextStoreEvent::ContextCreated(context_id) = event;
 626        let Some(context) = self
 627            .context_store
 628            .read(cx)
 629            .loaded_context_for_id(&context_id, cx)
 630        else {
 631            log::error!("no context found with ID: {}", context_id.to_proto());
 632            return;
 633        };
 634        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx).log_err();
 635
 636        let assistant_panel = cx.view().downgrade();
 637        let editor = cx.new_view(|cx| {
 638            let mut editor = ContextEditor::for_context(
 639                context,
 640                self.fs.clone(),
 641                self.workspace.clone(),
 642                self.project.clone(),
 643                lsp_adapter_delegate,
 644                assistant_panel,
 645                cx,
 646            );
 647            editor.insert_default_prompt(cx);
 648            editor
 649        });
 650
 651        self.show_context(editor.clone(), cx);
 652    }
 653
 654    fn completion_provider_changed(&mut self, cx: &mut ViewContext<Self>) {
 655        if let Some(editor) = self.active_context_editor(cx) {
 656            editor.update(cx, |active_context, cx| {
 657                active_context
 658                    .context
 659                    .update(cx, |context, cx| context.completion_provider_changed(cx))
 660            })
 661        }
 662
 663        let Some(new_provider_id) = LanguageModelRegistry::read_global(cx)
 664            .active_provider()
 665            .map(|p| p.id())
 666        else {
 667            return;
 668        };
 669
 670        if self
 671            .authenticate_provider_task
 672            .as_ref()
 673            .map_or(true, |(old_provider_id, _)| {
 674                *old_provider_id != new_provider_id
 675            })
 676        {
 677            self.authenticate_provider_task = None;
 678            self.ensure_authenticated(cx);
 679        }
 680
 681        if let Some(status) = self.client_status {
 682            self.update_zed_ai_notice_visibility(status, cx);
 683        }
 684    }
 685
 686    fn ensure_authenticated(&mut self, cx: &mut ViewContext<Self>) {
 687        if self.is_authenticated(cx) {
 688            return;
 689        }
 690
 691        let Some(provider) = LanguageModelRegistry::read_global(cx).active_provider() else {
 692            return;
 693        };
 694
 695        let load_credentials = self.authenticate(cx);
 696
 697        if self.authenticate_provider_task.is_none() {
 698            self.authenticate_provider_task = Some((
 699                provider.id(),
 700                cx.spawn(|this, mut cx| async move {
 701                    let _ = load_credentials.await;
 702                    this.update(&mut cx, |this, _cx| {
 703                        this.authenticate_provider_task = None;
 704                    })
 705                    .log_err();
 706                }),
 707            ));
 708        }
 709    }
 710
 711    pub fn inline_assist(
 712        workspace: &mut Workspace,
 713        action: &InlineAssist,
 714        cx: &mut ViewContext<Workspace>,
 715    ) {
 716        let settings = AssistantSettings::get_global(cx);
 717        if !settings.enabled {
 718            return;
 719        }
 720
 721        let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
 722            return;
 723        };
 724
 725        let Some(inline_assist_target) =
 726            Self::resolve_inline_assist_target(workspace, &assistant_panel, cx)
 727        else {
 728            return;
 729        };
 730
 731        let initial_prompt = action.prompt.clone();
 732        if assistant_panel.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
 733            match inline_assist_target {
 734                InlineAssistTarget::Editor(active_editor, include_context) => {
 735                    InlineAssistant::update_global(cx, |assistant, cx| {
 736                        assistant.assist(
 737                            &active_editor,
 738                            Some(cx.view().downgrade()),
 739                            include_context.then_some(&assistant_panel),
 740                            initial_prompt,
 741                            cx,
 742                        )
 743                    })
 744                }
 745                InlineAssistTarget::Terminal(active_terminal) => {
 746                    TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 747                        assistant.assist(
 748                            &active_terminal,
 749                            Some(cx.view().downgrade()),
 750                            Some(&assistant_panel),
 751                            initial_prompt,
 752                            cx,
 753                        )
 754                    })
 755                }
 756            }
 757        } else {
 758            let assistant_panel = assistant_panel.downgrade();
 759            cx.spawn(|workspace, mut cx| async move {
 760                assistant_panel
 761                    .update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
 762                    .await?;
 763                if assistant_panel.update(&mut cx, |panel, cx| panel.is_authenticated(cx))? {
 764                    cx.update(|cx| match inline_assist_target {
 765                        InlineAssistTarget::Editor(active_editor, include_context) => {
 766                            let assistant_panel = if include_context {
 767                                assistant_panel.upgrade()
 768                            } else {
 769                                None
 770                            };
 771                            InlineAssistant::update_global(cx, |assistant, cx| {
 772                                assistant.assist(
 773                                    &active_editor,
 774                                    Some(workspace),
 775                                    assistant_panel.as_ref(),
 776                                    initial_prompt,
 777                                    cx,
 778                                )
 779                            })
 780                        }
 781                        InlineAssistTarget::Terminal(active_terminal) => {
 782                            TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 783                                assistant.assist(
 784                                    &active_terminal,
 785                                    Some(workspace),
 786                                    assistant_panel.upgrade().as_ref(),
 787                                    initial_prompt,
 788                                    cx,
 789                                )
 790                            })
 791                        }
 792                    })?
 793                } else {
 794                    workspace.update(&mut cx, |workspace, cx| {
 795                        workspace.focus_panel::<AssistantPanel>(cx)
 796                    })?;
 797                }
 798
 799                anyhow::Ok(())
 800            })
 801            .detach_and_log_err(cx)
 802        }
 803    }
 804
 805    fn resolve_inline_assist_target(
 806        workspace: &mut Workspace,
 807        assistant_panel: &View<AssistantPanel>,
 808        cx: &mut WindowContext,
 809    ) -> Option<InlineAssistTarget> {
 810        if let Some(terminal_panel) = workspace.panel::<TerminalPanel>(cx) {
 811            if terminal_panel
 812                .read(cx)
 813                .focus_handle(cx)
 814                .contains_focused(cx)
 815            {
 816                if let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| {
 817                    pane.read(cx)
 818                        .active_item()
 819                        .and_then(|t| t.downcast::<TerminalView>())
 820                }) {
 821                    return Some(InlineAssistTarget::Terminal(terminal_view));
 822                }
 823            }
 824        }
 825        let context_editor =
 826            assistant_panel
 827                .read(cx)
 828                .active_context_editor(cx)
 829                .and_then(|editor| {
 830                    let editor = &editor.read(cx).editor;
 831                    if editor.read(cx).is_focused(cx) {
 832                        Some(editor.clone())
 833                    } else {
 834                        None
 835                    }
 836                });
 837
 838        if let Some(context_editor) = context_editor {
 839            Some(InlineAssistTarget::Editor(context_editor, false))
 840        } else if let Some(workspace_editor) = workspace
 841            .active_item(cx)
 842            .and_then(|item| item.act_as::<Editor>(cx))
 843        {
 844            Some(InlineAssistTarget::Editor(workspace_editor, true))
 845        } else if let Some(terminal_view) = workspace
 846            .active_item(cx)
 847            .and_then(|item| item.act_as::<TerminalView>(cx))
 848        {
 849            Some(InlineAssistTarget::Terminal(terminal_view))
 850        } else {
 851            None
 852        }
 853    }
 854
 855    fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
 856        if self.project.read(cx).is_remote() {
 857            let task = self
 858                .context_store
 859                .update(cx, |store, cx| store.create_remote_context(cx));
 860
 861            cx.spawn(|this, mut cx| async move {
 862                let context = task.await?;
 863
 864                this.update(&mut cx, |this, cx| {
 865                    let workspace = this.workspace.clone();
 866                    let project = this.project.clone();
 867                    let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err();
 868
 869                    let fs = this.fs.clone();
 870                    let project = this.project.clone();
 871                    let weak_assistant_panel = cx.view().downgrade();
 872
 873                    let editor = cx.new_view(|cx| {
 874                        ContextEditor::for_context(
 875                            context,
 876                            fs,
 877                            workspace,
 878                            project,
 879                            lsp_adapter_delegate,
 880                            weak_assistant_panel,
 881                            cx,
 882                        )
 883                    });
 884
 885                    this.show_context(editor, cx);
 886
 887                    anyhow::Ok(())
 888                })??;
 889
 890                anyhow::Ok(())
 891            })
 892            .detach_and_log_err(cx);
 893
 894            None
 895        } else {
 896            let context = self.context_store.update(cx, |store, cx| store.create(cx));
 897            let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx).log_err();
 898
 899            let assistant_panel = cx.view().downgrade();
 900            let editor = cx.new_view(|cx| {
 901                let mut editor = ContextEditor::for_context(
 902                    context,
 903                    self.fs.clone(),
 904                    self.workspace.clone(),
 905                    self.project.clone(),
 906                    lsp_adapter_delegate,
 907                    assistant_panel,
 908                    cx,
 909                );
 910                editor.insert_default_prompt(cx);
 911                editor
 912            });
 913
 914            self.show_context(editor.clone(), cx);
 915            Some(editor)
 916        }
 917    }
 918
 919    fn show_context(&mut self, context_editor: View<ContextEditor>, cx: &mut ViewContext<Self>) {
 920        let focus = self.focus_handle(cx).contains_focused(cx);
 921        let prev_len = self.pane.read(cx).items_len();
 922        self.pane.update(cx, |pane, cx| {
 923            pane.add_item(Box::new(context_editor.clone()), focus, focus, None, cx)
 924        });
 925
 926        if prev_len != self.pane.read(cx).items_len() {
 927            self.subscriptions
 928                .push(cx.subscribe(&context_editor, Self::handle_context_editor_event));
 929        }
 930
 931        self.show_updated_summary(&context_editor, cx);
 932
 933        cx.emit(AssistantPanelEvent::ContextEdited);
 934        cx.notify();
 935    }
 936
 937    fn show_updated_summary(
 938        &self,
 939        context_editor: &View<ContextEditor>,
 940        cx: &mut ViewContext<Self>,
 941    ) {
 942        context_editor.update(cx, |context_editor, cx| {
 943            let new_summary = context_editor.title(cx).to_string();
 944            self.model_summary_editor.update(cx, |summary_editor, cx| {
 945                if summary_editor.text(cx) != new_summary {
 946                    summary_editor.set_text(new_summary, cx);
 947                }
 948            });
 949        });
 950    }
 951
 952    fn handle_context_editor_event(
 953        &mut self,
 954        context_editor: View<ContextEditor>,
 955        event: &EditorEvent,
 956        cx: &mut ViewContext<Self>,
 957    ) {
 958        match event {
 959            EditorEvent::TitleChanged => {
 960                self.show_updated_summary(&context_editor, cx);
 961                cx.notify()
 962            }
 963            EditorEvent::Edited { .. } => cx.emit(AssistantPanelEvent::ContextEdited),
 964            _ => {}
 965        }
 966    }
 967
 968    fn show_configuration(
 969        workspace: &mut Workspace,
 970        _: &ShowConfiguration,
 971        cx: &mut ViewContext<Workspace>,
 972    ) {
 973        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
 974            return;
 975        };
 976
 977        if !panel.focus_handle(cx).contains_focused(cx) {
 978            workspace.toggle_panel_focus::<AssistantPanel>(cx);
 979        }
 980
 981        panel.update(cx, |this, cx| {
 982            this.show_configuration_tab(cx);
 983        })
 984    }
 985
 986    fn show_configuration_tab(&mut self, cx: &mut ViewContext<Self>) {
 987        let configuration_item_ix = self
 988            .pane
 989            .read(cx)
 990            .items()
 991            .position(|item| item.downcast::<ConfigurationView>().is_some());
 992
 993        if let Some(configuration_item_ix) = configuration_item_ix {
 994            self.pane.update(cx, |pane, cx| {
 995                pane.activate_item(configuration_item_ix, true, true, cx);
 996            });
 997        } else {
 998            let configuration = cx.new_view(|cx| ConfigurationView::new(cx));
 999            self.configuration_subscription = Some(cx.subscribe(
1000                &configuration,
1001                |this, _, event: &ConfigurationViewEvent, cx| match event {
1002                    ConfigurationViewEvent::NewProviderContextEditor(provider) => {
1003                        if LanguageModelRegistry::read_global(cx)
1004                            .active_provider()
1005                            .map_or(true, |p| p.id() != provider.id())
1006                        {
1007                            if let Some(model) = provider.provided_models(cx).first().cloned() {
1008                                update_settings_file::<AssistantSettings>(
1009                                    this.fs.clone(),
1010                                    cx,
1011                                    move |settings, _| settings.set_model(model),
1012                                );
1013                            }
1014                        }
1015
1016                        this.new_context(cx);
1017                    }
1018                },
1019            ));
1020            self.pane.update(cx, |pane, cx| {
1021                pane.add_item(Box::new(configuration), true, true, None, cx);
1022            });
1023        }
1024    }
1025
1026    fn deploy_history(&mut self, _: &DeployHistory, cx: &mut ViewContext<Self>) {
1027        let history_item_ix = self
1028            .pane
1029            .read(cx)
1030            .items()
1031            .position(|item| item.downcast::<ContextHistory>().is_some());
1032
1033        if let Some(history_item_ix) = history_item_ix {
1034            self.pane.update(cx, |pane, cx| {
1035                pane.activate_item(history_item_ix, true, true, cx);
1036            });
1037        } else {
1038            let assistant_panel = cx.view().downgrade();
1039            let history = cx.new_view(|cx| {
1040                ContextHistory::new(
1041                    self.project.clone(),
1042                    self.context_store.clone(),
1043                    assistant_panel,
1044                    cx,
1045                )
1046            });
1047            self.pane.update(cx, |pane, cx| {
1048                pane.add_item(Box::new(history), true, true, None, cx);
1049            });
1050        }
1051    }
1052
1053    fn deploy_prompt_library(&mut self, _: &DeployPromptLibrary, cx: &mut ViewContext<Self>) {
1054        open_prompt_library(self.languages.clone(), cx).detach_and_log_err(cx);
1055    }
1056
1057    fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
1058        self.model_selector_menu_handle.toggle(cx);
1059    }
1060
1061    fn active_context_editor(&self, cx: &AppContext) -> Option<View<ContextEditor>> {
1062        self.pane
1063            .read(cx)
1064            .active_item()?
1065            .downcast::<ContextEditor>()
1066    }
1067
1068    pub fn active_context(&self, cx: &AppContext) -> Option<Model<Context>> {
1069        Some(self.active_context_editor(cx)?.read(cx).context.clone())
1070    }
1071
1072    fn open_saved_context(
1073        &mut self,
1074        path: PathBuf,
1075        cx: &mut ViewContext<Self>,
1076    ) -> Task<Result<()>> {
1077        let existing_context = self.pane.read(cx).items().find_map(|item| {
1078            item.downcast::<ContextEditor>()
1079                .filter(|editor| editor.read(cx).context.read(cx).path() == Some(&path))
1080        });
1081        if let Some(existing_context) = existing_context {
1082            return cx.spawn(|this, mut cx| async move {
1083                this.update(&mut cx, |this, cx| this.show_context(existing_context, cx))
1084            });
1085        }
1086
1087        let context = self
1088            .context_store
1089            .update(cx, |store, cx| store.open_local_context(path.clone(), cx));
1090        let fs = self.fs.clone();
1091        let project = self.project.clone();
1092        let workspace = self.workspace.clone();
1093
1094        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err();
1095
1096        cx.spawn(|this, mut cx| async move {
1097            let context = context.await?;
1098            let assistant_panel = this.clone();
1099            this.update(&mut cx, |this, cx| {
1100                let editor = cx.new_view(|cx| {
1101                    ContextEditor::for_context(
1102                        context,
1103                        fs,
1104                        workspace,
1105                        project,
1106                        lsp_adapter_delegate,
1107                        assistant_panel,
1108                        cx,
1109                    )
1110                });
1111                this.show_context(editor, cx);
1112                anyhow::Ok(())
1113            })??;
1114            Ok(())
1115        })
1116    }
1117
1118    fn open_remote_context(
1119        &mut self,
1120        id: ContextId,
1121        cx: &mut ViewContext<Self>,
1122    ) -> Task<Result<View<ContextEditor>>> {
1123        let existing_context = self.pane.read(cx).items().find_map(|item| {
1124            item.downcast::<ContextEditor>()
1125                .filter(|editor| *editor.read(cx).context.read(cx).id() == id)
1126        });
1127        if let Some(existing_context) = existing_context {
1128            return cx.spawn(|this, mut cx| async move {
1129                this.update(&mut cx, |this, cx| {
1130                    this.show_context(existing_context.clone(), cx)
1131                })?;
1132                Ok(existing_context)
1133            });
1134        }
1135
1136        let context = self
1137            .context_store
1138            .update(cx, |store, cx| store.open_remote_context(id, cx));
1139        let fs = self.fs.clone();
1140        let workspace = self.workspace.clone();
1141        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx).log_err();
1142
1143        cx.spawn(|this, mut cx| async move {
1144            let context = context.await?;
1145            let assistant_panel = this.clone();
1146            this.update(&mut cx, |this, cx| {
1147                let editor = cx.new_view(|cx| {
1148                    ContextEditor::for_context(
1149                        context,
1150                        fs,
1151                        workspace,
1152                        this.project.clone(),
1153                        lsp_adapter_delegate,
1154                        assistant_panel,
1155                        cx,
1156                    )
1157                });
1158                this.show_context(editor.clone(), cx);
1159                anyhow::Ok(editor)
1160            })?
1161        })
1162    }
1163
1164    fn is_authenticated(&mut self, cx: &mut ViewContext<Self>) -> bool {
1165        LanguageModelRegistry::read_global(cx)
1166            .active_provider()
1167            .map_or(false, |provider| provider.is_authenticated(cx))
1168    }
1169
1170    fn authenticate(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
1171        LanguageModelRegistry::read_global(cx)
1172            .active_provider()
1173            .map_or(
1174                Task::ready(Err(anyhow!("no active language model provider"))),
1175                |provider| provider.authenticate(cx),
1176            )
1177    }
1178}
1179
1180impl Render for AssistantPanel {
1181    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1182        let mut registrar = DivRegistrar::new(
1183            |panel, cx| {
1184                panel
1185                    .pane
1186                    .read(cx)
1187                    .toolbar()
1188                    .read(cx)
1189                    .item_of_type::<BufferSearchBar>()
1190            },
1191            cx,
1192        );
1193        BufferSearchBar::register(&mut registrar);
1194        let registrar = registrar.into_div();
1195
1196        v_flex()
1197            .key_context("AssistantPanel")
1198            .size_full()
1199            .on_action(cx.listener(|this, _: &workspace::NewFile, cx| {
1200                this.new_context(cx);
1201            }))
1202            .on_action(
1203                cx.listener(|this, _: &ShowConfiguration, cx| this.show_configuration_tab(cx)),
1204            )
1205            .on_action(cx.listener(AssistantPanel::deploy_history))
1206            .on_action(cx.listener(AssistantPanel::deploy_prompt_library))
1207            .on_action(cx.listener(AssistantPanel::toggle_model_selector))
1208            .child(registrar.size_full().child(self.pane.clone()))
1209            .into_any_element()
1210    }
1211}
1212
1213impl Panel for AssistantPanel {
1214    fn persistent_name() -> &'static str {
1215        "AssistantPanel"
1216    }
1217
1218    fn position(&self, cx: &WindowContext) -> DockPosition {
1219        match AssistantSettings::get_global(cx).dock {
1220            AssistantDockPosition::Left => DockPosition::Left,
1221            AssistantDockPosition::Bottom => DockPosition::Bottom,
1222            AssistantDockPosition::Right => DockPosition::Right,
1223        }
1224    }
1225
1226    fn position_is_valid(&self, _: DockPosition) -> bool {
1227        true
1228    }
1229
1230    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
1231        settings::update_settings_file::<AssistantSettings>(
1232            self.fs.clone(),
1233            cx,
1234            move |settings, _| {
1235                let dock = match position {
1236                    DockPosition::Left => AssistantDockPosition::Left,
1237                    DockPosition::Bottom => AssistantDockPosition::Bottom,
1238                    DockPosition::Right => AssistantDockPosition::Right,
1239                };
1240                settings.set_dock(dock);
1241            },
1242        );
1243    }
1244
1245    fn size(&self, cx: &WindowContext) -> Pixels {
1246        let settings = AssistantSettings::get_global(cx);
1247        match self.position(cx) {
1248            DockPosition::Left | DockPosition::Right => {
1249                self.width.unwrap_or(settings.default_width)
1250            }
1251            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1252        }
1253    }
1254
1255    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
1256        match self.position(cx) {
1257            DockPosition::Left | DockPosition::Right => self.width = size,
1258            DockPosition::Bottom => self.height = size,
1259        }
1260        cx.notify();
1261    }
1262
1263    fn is_zoomed(&self, cx: &WindowContext) -> bool {
1264        self.pane.read(cx).is_zoomed()
1265    }
1266
1267    fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
1268        self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
1269    }
1270
1271    fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
1272        if active {
1273            if self.pane.read(cx).items_len() == 0 {
1274                self.new_context(cx);
1275            }
1276
1277            self.ensure_authenticated(cx);
1278        }
1279    }
1280
1281    fn pane(&self) -> Option<View<Pane>> {
1282        Some(self.pane.clone())
1283    }
1284
1285    fn remote_id() -> Option<proto::PanelId> {
1286        Some(proto::PanelId::AssistantPanel)
1287    }
1288
1289    fn icon(&self, cx: &WindowContext) -> Option<IconName> {
1290        let settings = AssistantSettings::get_global(cx);
1291        if !settings.enabled || !settings.button {
1292            return None;
1293        }
1294
1295        Some(IconName::ZedAssistant)
1296    }
1297
1298    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
1299        Some("Assistant Panel")
1300    }
1301
1302    fn toggle_action(&self) -> Box<dyn Action> {
1303        Box::new(ToggleFocus)
1304    }
1305}
1306
1307impl EventEmitter<PanelEvent> for AssistantPanel {}
1308impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
1309
1310impl FocusableView for AssistantPanel {
1311    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
1312        self.pane.focus_handle(cx)
1313    }
1314}
1315
1316pub enum ContextEditorEvent {
1317    Edited,
1318    TabContentChanged,
1319}
1320
1321#[derive(Copy, Clone, Debug, PartialEq)]
1322struct ScrollPosition {
1323    offset_before_cursor: gpui::Point<f32>,
1324    cursor: Anchor,
1325}
1326
1327struct WorkflowAssist {
1328    editor: WeakView<Editor>,
1329    editor_was_open: bool,
1330    assist_ids: Vec<InlineAssistId>,
1331}
1332
1333pub struct ContextEditor {
1334    context: Model<Context>,
1335    fs: Arc<dyn Fs>,
1336    workspace: WeakView<Workspace>,
1337    project: Model<Project>,
1338    lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1339    editor: View<Editor>,
1340    blocks: HashSet<CustomBlockId>,
1341    scroll_position: Option<ScrollPosition>,
1342    remote_id: Option<workspace::ViewId>,
1343    pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
1344    pending_slash_command_blocks: HashMap<Range<language::Anchor>, CustomBlockId>,
1345    workflow_assists: HashMap<Range<language::Anchor>, WorkflowAssist>,
1346    active_workflow_step_range: Option<Range<language::Anchor>>,
1347    _subscriptions: Vec<Subscription>,
1348    assistant_panel: WeakView<AssistantPanel>,
1349    error_message: Option<SharedString>,
1350}
1351
1352const DEFAULT_TAB_TITLE: &str = "New Context";
1353const MAX_TAB_TITLE_LEN: usize = 16;
1354
1355impl ContextEditor {
1356    fn for_context(
1357        context: Model<Context>,
1358        fs: Arc<dyn Fs>,
1359        workspace: WeakView<Workspace>,
1360        project: Model<Project>,
1361        lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1362        assistant_panel: WeakView<AssistantPanel>,
1363        cx: &mut ViewContext<Self>,
1364    ) -> Self {
1365        let completion_provider = SlashCommandCompletionProvider::new(
1366            Some(cx.view().downgrade()),
1367            Some(workspace.clone()),
1368        );
1369
1370        let editor = cx.new_view(|cx| {
1371            let mut editor = Editor::for_buffer(context.read(cx).buffer().clone(), None, cx);
1372            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
1373            editor.set_show_line_numbers(false, cx);
1374            editor.set_show_git_diff_gutter(false, cx);
1375            editor.set_show_code_actions(false, cx);
1376            editor.set_show_runnables(false, cx);
1377            editor.set_show_wrap_guides(false, cx);
1378            editor.set_show_indent_guides(false, cx);
1379            editor.set_completion_provider(Box::new(completion_provider));
1380            editor.set_collaboration_hub(Box::new(project.clone()));
1381            editor
1382        });
1383
1384        let _subscriptions = vec![
1385            cx.observe(&context, |_, _, cx| cx.notify()),
1386            cx.subscribe(&context, Self::handle_context_event),
1387            cx.subscribe(&editor, Self::handle_editor_event),
1388            cx.subscribe(&editor, Self::handle_editor_search_event),
1389        ];
1390
1391        let sections = context.read(cx).slash_command_output_sections().to_vec();
1392        let mut this = Self {
1393            context,
1394            editor,
1395            lsp_adapter_delegate,
1396            blocks: Default::default(),
1397            scroll_position: None,
1398            remote_id: None,
1399            fs,
1400            workspace,
1401            project,
1402            pending_slash_command_creases: HashMap::default(),
1403            pending_slash_command_blocks: HashMap::default(),
1404            _subscriptions,
1405            workflow_assists: HashMap::default(),
1406            active_workflow_step_range: None,
1407            assistant_panel,
1408            error_message: None,
1409        };
1410        this.update_message_headers(cx);
1411        this.insert_slash_command_output_sections(sections, cx);
1412        this
1413    }
1414
1415    fn insert_default_prompt(&mut self, cx: &mut ViewContext<Self>) {
1416        let command_name = DefaultSlashCommand.name();
1417        self.editor.update(cx, |editor, cx| {
1418            editor.insert(&format!("/{command_name}"), cx)
1419        });
1420        self.split(&Split, cx);
1421        let command = self.context.update(cx, |context, cx| {
1422            let first_message_id = context.messages(cx).next().unwrap().id;
1423            context.update_metadata(first_message_id, cx, |metadata| {
1424                metadata.role = Role::System;
1425            });
1426            context.reparse_slash_commands(cx);
1427            context.pending_slash_commands()[0].clone()
1428        });
1429
1430        self.run_command(
1431            command.source_range,
1432            &command.name,
1433            command.argument.as_deref(),
1434            false,
1435            self.workspace.clone(),
1436            cx,
1437        );
1438    }
1439
1440    fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
1441        if !self.apply_workflow_step(cx) {
1442            self.error_message = None;
1443            self.send_to_model(cx);
1444            cx.notify();
1445        }
1446    }
1447
1448    fn apply_workflow_step(&mut self, cx: &mut ViewContext<Self>) -> bool {
1449        if let Some(step_range) = self.active_workflow_step_range.as_ref() {
1450            if let Some(assists) = self.workflow_assists.get(&step_range) {
1451                let assist_ids = assists.assist_ids.clone();
1452                cx.window_context().defer(|cx| {
1453                    InlineAssistant::update_global(cx, |assistant, cx| {
1454                        for assist_id in assist_ids {
1455                            assistant.start_assist(assist_id, cx);
1456                        }
1457                    })
1458                });
1459
1460                !assists.assist_ids.is_empty()
1461            } else {
1462                false
1463            }
1464        } else {
1465            false
1466        }
1467    }
1468
1469    fn send_to_model(&mut self, cx: &mut ViewContext<Self>) {
1470        if let Some(user_message) = self.context.update(cx, |context, cx| context.assist(cx)) {
1471            let new_selection = {
1472                let cursor = user_message
1473                    .start
1474                    .to_offset(self.context.read(cx).buffer().read(cx));
1475                cursor..cursor
1476            };
1477            self.editor.update(cx, |editor, cx| {
1478                editor.change_selections(
1479                    Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
1480                    cx,
1481                    |selections| selections.select_ranges([new_selection]),
1482                );
1483            });
1484            // Avoid scrolling to the new cursor position so the assistant's output is stable.
1485            cx.defer(|this, _| this.scroll_position = None);
1486        }
1487    }
1488
1489    fn cancel_last_assist(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
1490        if !self
1491            .context
1492            .update(cx, |context, _| context.cancel_last_assist())
1493        {
1494            cx.propagate();
1495        }
1496    }
1497
1498    fn debug_edit_steps(&mut self, _: &DebugEditSteps, cx: &mut ViewContext<Self>) {
1499        let mut output = String::new();
1500        for (i, step) in self.context.read(cx).workflow_steps().iter().enumerate() {
1501            output.push_str(&format!("Step {}:\n", i + 1));
1502            output.push_str(&format!(
1503                "Content: {}\n",
1504                self.context
1505                    .read(cx)
1506                    .buffer()
1507                    .read(cx)
1508                    .text_for_range(step.tagged_range.clone())
1509                    .collect::<String>()
1510            ));
1511            match &step.status {
1512                WorkflowStepStatus::Resolved(ResolvedWorkflowStep { title, suggestions }) => {
1513                    output.push_str("Resolution:\n");
1514                    output.push_str(&format!("  {:?}\n", title));
1515                    output.push_str(&format!("  {:?}\n", suggestions));
1516                }
1517                WorkflowStepStatus::Pending(_) => {
1518                    output.push_str("Resolution: Pending\n");
1519                }
1520            }
1521            output.push('\n');
1522        }
1523
1524        let editor = self
1525            .workspace
1526            .update(cx, |workspace, cx| Editor::new_in_workspace(workspace, cx));
1527
1528        if let Ok(editor) = editor {
1529            cx.spawn(|_, mut cx| async move {
1530                let editor = editor.await?;
1531                editor.update(&mut cx, |editor, cx| editor.set_text(output, cx))
1532            })
1533            .detach_and_notify_err(cx);
1534        }
1535    }
1536
1537    fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
1538        let cursors = self.cursors(cx);
1539        self.context.update(cx, |context, cx| {
1540            let messages = context
1541                .messages_for_offsets(cursors, cx)
1542                .into_iter()
1543                .map(|message| message.id)
1544                .collect();
1545            context.cycle_message_roles(messages, cx)
1546        });
1547    }
1548
1549    fn cursors(&self, cx: &AppContext) -> Vec<usize> {
1550        let selections = self.editor.read(cx).selections.all::<usize>(cx);
1551        selections
1552            .into_iter()
1553            .map(|selection| selection.head())
1554            .collect()
1555    }
1556
1557    fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
1558        if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
1559            self.editor.update(cx, |editor, cx| {
1560                editor.transact(cx, |editor, cx| {
1561                    editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel());
1562                    let snapshot = editor.buffer().read(cx).snapshot(cx);
1563                    let newest_cursor = editor.selections.newest::<Point>(cx).head();
1564                    if newest_cursor.column > 0
1565                        || snapshot
1566                            .chars_at(newest_cursor)
1567                            .next()
1568                            .map_or(false, |ch| ch != '\n')
1569                    {
1570                        editor.move_to_end_of_line(
1571                            &MoveToEndOfLine {
1572                                stop_at_soft_wraps: false,
1573                            },
1574                            cx,
1575                        );
1576                        editor.newline(&Newline, cx);
1577                    }
1578
1579                    editor.insert(&format!("/{name}"), cx);
1580                    if command.requires_argument() {
1581                        editor.insert(" ", cx);
1582                        editor.show_completions(&ShowCompletions::default(), cx);
1583                    }
1584                });
1585            });
1586            if !command.requires_argument() {
1587                self.confirm_command(&ConfirmCommand, cx);
1588            }
1589        }
1590    }
1591
1592    pub fn confirm_command(&mut self, _: &ConfirmCommand, cx: &mut ViewContext<Self>) {
1593        let selections = self.editor.read(cx).selections.disjoint_anchors();
1594        let mut commands_by_range = HashMap::default();
1595        let workspace = self.workspace.clone();
1596        self.context.update(cx, |context, cx| {
1597            context.reparse_slash_commands(cx);
1598            for selection in selections.iter() {
1599                if let Some(command) =
1600                    context.pending_command_for_position(selection.head().text_anchor, cx)
1601                {
1602                    commands_by_range
1603                        .entry(command.source_range.clone())
1604                        .or_insert_with(|| command.clone());
1605                }
1606            }
1607        });
1608
1609        if commands_by_range.is_empty() {
1610            cx.propagate();
1611        } else {
1612            for command in commands_by_range.into_values() {
1613                self.run_command(
1614                    command.source_range,
1615                    &command.name,
1616                    command.argument.as_deref(),
1617                    true,
1618                    workspace.clone(),
1619                    cx,
1620                );
1621            }
1622            cx.stop_propagation();
1623        }
1624    }
1625
1626    pub fn run_command(
1627        &mut self,
1628        command_range: Range<language::Anchor>,
1629        name: &str,
1630        argument: Option<&str>,
1631        insert_trailing_newline: bool,
1632        workspace: WeakView<Workspace>,
1633        cx: &mut ViewContext<Self>,
1634    ) {
1635        if let Some(command) = SlashCommandRegistry::global(cx).command(name) {
1636            let argument = argument.map(ToString::to_string);
1637            let output = command.run(
1638                argument.as_deref(),
1639                workspace,
1640                self.lsp_adapter_delegate.clone(),
1641                cx,
1642            );
1643            self.context.update(cx, |context, cx| {
1644                context.insert_command_output(command_range, output, insert_trailing_newline, cx)
1645            });
1646        }
1647    }
1648
1649    fn handle_context_event(
1650        &mut self,
1651        _: Model<Context>,
1652        event: &ContextEvent,
1653        cx: &mut ViewContext<Self>,
1654    ) {
1655        let context_editor = cx.view().downgrade();
1656
1657        match event {
1658            ContextEvent::MessagesEdited => {
1659                self.update_message_headers(cx);
1660                self.context.update(cx, |context, cx| {
1661                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
1662                });
1663            }
1664            ContextEvent::WorkflowStepsChanged => {
1665                self.update_active_workflow_step_from_cursor(cx);
1666                cx.notify();
1667            }
1668            ContextEvent::SummaryChanged => {
1669                cx.emit(EditorEvent::TitleChanged);
1670                self.context.update(cx, |context, cx| {
1671                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
1672                });
1673            }
1674            ContextEvent::StreamedCompletion => {
1675                self.editor.update(cx, |editor, cx| {
1676                    if let Some(scroll_position) = self.scroll_position {
1677                        let snapshot = editor.snapshot(cx);
1678                        let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
1679                        let scroll_top =
1680                            cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
1681                        editor.set_scroll_position(
1682                            point(scroll_position.offset_before_cursor.x, scroll_top),
1683                            cx,
1684                        );
1685                    }
1686                });
1687            }
1688            ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
1689                self.editor.update(cx, |editor, cx| {
1690                    let buffer = editor.buffer().read(cx).snapshot(cx);
1691                    let (excerpt_id, buffer_id, _) = buffer.as_singleton().unwrap();
1692                    let excerpt_id = *excerpt_id;
1693
1694                    editor.remove_creases(
1695                        removed
1696                            .iter()
1697                            .filter_map(|range| self.pending_slash_command_creases.remove(range)),
1698                        cx,
1699                    );
1700
1701                    editor.remove_blocks(
1702                        HashSet::from_iter(
1703                            removed.iter().filter_map(|range| {
1704                                self.pending_slash_command_blocks.remove(range)
1705                            }),
1706                        ),
1707                        None,
1708                        cx,
1709                    );
1710
1711                    let crease_ids = editor.insert_creases(
1712                        updated.iter().map(|command| {
1713                            let workspace = self.workspace.clone();
1714                            let confirm_command = Arc::new({
1715                                let context_editor = context_editor.clone();
1716                                let command = command.clone();
1717                                move |cx: &mut WindowContext| {
1718                                    context_editor
1719                                        .update(cx, |context_editor, cx| {
1720                                            context_editor.run_command(
1721                                                command.source_range.clone(),
1722                                                &command.name,
1723                                                command.argument.as_deref(),
1724                                                false,
1725                                                workspace.clone(),
1726                                                cx,
1727                                            );
1728                                        })
1729                                        .ok();
1730                                }
1731                            });
1732                            let placeholder = FoldPlaceholder {
1733                                render: Arc::new(move |_, _, _| Empty.into_any()),
1734                                constrain_width: false,
1735                                merge_adjacent: false,
1736                            };
1737                            let render_toggle = {
1738                                let confirm_command = confirm_command.clone();
1739                                let command = command.clone();
1740                                move |row, _, _, _cx: &mut WindowContext| {
1741                                    render_pending_slash_command_gutter_decoration(
1742                                        row,
1743                                        &command.status,
1744                                        confirm_command.clone(),
1745                                    )
1746                                }
1747                            };
1748                            let render_trailer = {
1749                                let command = command.clone();
1750                                move |row, _unfold, cx: &mut WindowContext| {
1751                                    // TODO: In the future we should investigate how we can expose
1752                                    // this as a hook on the `SlashCommand` trait so that we don't
1753                                    // need to special-case it here.
1754                                    if command.name == DocsSlashCommand::NAME {
1755                                        return render_docs_slash_command_trailer(
1756                                            row,
1757                                            command.clone(),
1758                                            cx,
1759                                        );
1760                                    }
1761
1762                                    Empty.into_any()
1763                                }
1764                            };
1765
1766                            let start = buffer
1767                                .anchor_in_excerpt(excerpt_id, command.source_range.start)
1768                                .unwrap();
1769                            let end = buffer
1770                                .anchor_in_excerpt(excerpt_id, command.source_range.end)
1771                                .unwrap();
1772                            Crease::new(start..end, placeholder, render_toggle, render_trailer)
1773                        }),
1774                        cx,
1775                    );
1776
1777                    let block_ids = editor.insert_blocks(
1778                        updated
1779                            .iter()
1780                            .filter_map(|command| match &command.status {
1781                                PendingSlashCommandStatus::Error(error) => {
1782                                    Some((command, error.clone()))
1783                                }
1784                                _ => None,
1785                            })
1786                            .map(|(command, error_message)| BlockProperties {
1787                                style: BlockStyle::Fixed,
1788                                position: Anchor {
1789                                    buffer_id: Some(buffer_id),
1790                                    excerpt_id,
1791                                    text_anchor: command.source_range.start,
1792                                },
1793                                height: 1,
1794                                disposition: BlockDisposition::Below,
1795                                render: slash_command_error_block_renderer(error_message),
1796                            }),
1797                        None,
1798                        cx,
1799                    );
1800
1801                    self.pending_slash_command_creases.extend(
1802                        updated
1803                            .iter()
1804                            .map(|command| command.source_range.clone())
1805                            .zip(crease_ids),
1806                    );
1807
1808                    self.pending_slash_command_blocks.extend(
1809                        updated
1810                            .iter()
1811                            .map(|command| command.source_range.clone())
1812                            .zip(block_ids),
1813                    );
1814                })
1815            }
1816            ContextEvent::SlashCommandFinished {
1817                output_range,
1818                sections,
1819                run_commands_in_output,
1820            } => {
1821                self.insert_slash_command_output_sections(sections.iter().cloned(), cx);
1822
1823                if *run_commands_in_output {
1824                    let commands = self.context.update(cx, |context, cx| {
1825                        context.reparse_slash_commands(cx);
1826                        context
1827                            .pending_commands_for_range(output_range.clone(), cx)
1828                            .to_vec()
1829                    });
1830
1831                    for command in commands {
1832                        self.run_command(
1833                            command.source_range,
1834                            &command.name,
1835                            command.argument.as_deref(),
1836                            false,
1837                            self.workspace.clone(),
1838                            cx,
1839                        );
1840                    }
1841                }
1842            }
1843            ContextEvent::Operation(_) => {}
1844            ContextEvent::AssistError(error_message) => {
1845                self.error_message = Some(SharedString::from(error_message.clone()));
1846            }
1847        }
1848    }
1849
1850    fn insert_slash_command_output_sections(
1851        &mut self,
1852        sections: impl IntoIterator<Item = SlashCommandOutputSection<language::Anchor>>,
1853        cx: &mut ViewContext<Self>,
1854    ) {
1855        self.editor.update(cx, |editor, cx| {
1856            let buffer = editor.buffer().read(cx).snapshot(cx);
1857            let excerpt_id = *buffer.as_singleton().unwrap().0;
1858            let mut buffer_rows_to_fold = BTreeSet::new();
1859            let mut creases = Vec::new();
1860            for section in sections {
1861                let start = buffer
1862                    .anchor_in_excerpt(excerpt_id, section.range.start)
1863                    .unwrap();
1864                let end = buffer
1865                    .anchor_in_excerpt(excerpt_id, section.range.end)
1866                    .unwrap();
1867                let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
1868                buffer_rows_to_fold.insert(buffer_row);
1869                creases.push(Crease::new(
1870                    start..end,
1871                    FoldPlaceholder {
1872                        render: Arc::new({
1873                            let editor = cx.view().downgrade();
1874                            let icon = section.icon;
1875                            let label = section.label.clone();
1876                            move |fold_id, fold_range, _cx| {
1877                                let editor = editor.clone();
1878                                ButtonLike::new(fold_id)
1879                                    .style(ButtonStyle::Filled)
1880                                    .layer(ElevationIndex::ElevatedSurface)
1881                                    .child(Icon::new(icon))
1882                                    .child(Label::new(label.clone()).single_line())
1883                                    .on_click(move |_, cx| {
1884                                        editor
1885                                            .update(cx, |editor, cx| {
1886                                                let buffer_start = fold_range
1887                                                    .start
1888                                                    .to_point(&editor.buffer().read(cx).read(cx));
1889                                                let buffer_row = MultiBufferRow(buffer_start.row);
1890                                                editor.unfold_at(&UnfoldAt { buffer_row }, cx);
1891                                            })
1892                                            .ok();
1893                                    })
1894                                    .into_any_element()
1895                            }
1896                        }),
1897                        constrain_width: false,
1898                        merge_adjacent: false,
1899                    },
1900                    render_slash_command_output_toggle,
1901                    |_, _, _| Empty.into_any_element(),
1902                ));
1903            }
1904
1905            editor.insert_creases(creases, cx);
1906
1907            for buffer_row in buffer_rows_to_fold.into_iter().rev() {
1908                editor.fold_at(&FoldAt { buffer_row }, cx);
1909            }
1910        });
1911    }
1912
1913    fn handle_editor_event(
1914        &mut self,
1915        _: View<Editor>,
1916        event: &EditorEvent,
1917        cx: &mut ViewContext<Self>,
1918    ) {
1919        match event {
1920            EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
1921                let cursor_scroll_position = self.cursor_scroll_position(cx);
1922                if *autoscroll {
1923                    self.scroll_position = cursor_scroll_position;
1924                } else if self.scroll_position != cursor_scroll_position {
1925                    self.scroll_position = None;
1926                }
1927            }
1928            EditorEvent::SelectionsChanged { .. } => {
1929                self.scroll_position = self.cursor_scroll_position(cx);
1930                self.update_active_workflow_step_from_cursor(cx);
1931            }
1932            _ => {}
1933        }
1934        cx.emit(event.clone());
1935    }
1936
1937    fn update_active_workflow_step_from_cursor(&mut self, cx: &mut ViewContext<Self>) {
1938        let new_step = self
1939            .workflow_step_range_for_cursor(cx)
1940            .as_ref()
1941            .and_then(|step_range| {
1942                let workflow_step = self
1943                    .context
1944                    .read(cx)
1945                    .workflow_step_for_range(step_range.clone())?;
1946                Some(workflow_step.tagged_range.clone())
1947            });
1948        if new_step.as_ref() != self.active_workflow_step_range.as_ref() {
1949            if let Some(old_step_range) = self.active_workflow_step_range.take() {
1950                self.hide_workflow_step(old_step_range, cx);
1951            }
1952
1953            if let Some(new_step) = new_step {
1954                self.activate_workflow_step(new_step, cx);
1955            }
1956        }
1957    }
1958
1959    fn hide_workflow_step(
1960        &mut self,
1961        step_range: Range<language::Anchor>,
1962        cx: &mut ViewContext<Self>,
1963    ) {
1964        let Some(step_assist) = self.workflow_assists.get_mut(&step_range) else {
1965            return;
1966        };
1967        let Some(editor) = step_assist.editor.upgrade() else {
1968            self.workflow_assists.remove(&step_range);
1969            return;
1970        };
1971
1972        InlineAssistant::update_global(cx, |assistant, cx| {
1973            step_assist.assist_ids.retain(|assist_id| {
1974                match assistant.status_for_assist(*assist_id, cx) {
1975                    Some(CodegenStatus::Idle) | None => {
1976                        assistant.finish_assist(*assist_id, true, cx);
1977                        false
1978                    }
1979                    _ => true,
1980                }
1981            });
1982        });
1983
1984        if step_assist.assist_ids.is_empty() {
1985            let editor_was_open = step_assist.editor_was_open;
1986            self.workflow_assists.remove(&step_range);
1987            self.workspace
1988                .update(cx, |workspace, cx| {
1989                    if let Some(pane) = workspace.pane_for(&editor) {
1990                        pane.update(cx, |pane, cx| {
1991                            let item_id = editor.entity_id();
1992                            if !editor_was_open && pane.is_active_preview_item(item_id) {
1993                                pane.close_item_by_id(item_id, SaveIntent::Skip, cx)
1994                                    .detach_and_log_err(cx);
1995                            }
1996                        });
1997                    }
1998                })
1999                .ok();
2000        }
2001    }
2002
2003    fn activate_workflow_step(
2004        &mut self,
2005        step_range: Range<language::Anchor>,
2006        cx: &mut ViewContext<Self>,
2007    ) -> Option<()> {
2008        if self.scroll_to_existing_workflow_assist(&step_range, cx) {
2009            return None;
2010        }
2011
2012        let step = self
2013            .workflow_step(&step_range, cx)
2014            .with_context(|| format!("could not find workflow step for range {:?}", step_range))
2015            .log_err()?;
2016        let Some(resolved) = step.status.as_resolved() else {
2017            return None;
2018        };
2019
2020        let title = resolved.title.clone();
2021        let suggestions = resolved.suggestions.clone();
2022
2023        if let Some((editor, assist_ids, editor_was_open)) = {
2024            let assistant_panel = self.assistant_panel.upgrade()?;
2025            if suggestions.is_empty() {
2026                return None;
2027            }
2028
2029            let editor;
2030            let mut editor_was_open = false;
2031            let mut suggestion_groups = Vec::new();
2032            if suggestions.len() == 1 && suggestions.values().next().unwrap().len() == 1 {
2033                // If there's only one buffer and one suggestion group, open it directly
2034                let (buffer, groups) = suggestions.into_iter().next().unwrap();
2035                let group = groups.into_iter().next().unwrap();
2036                editor = self
2037                    .workspace
2038                    .update(cx, |workspace, cx| {
2039                        let active_pane = workspace.active_pane().clone();
2040                        editor_was_open =
2041                            workspace.is_project_item_open::<Editor>(&active_pane, &buffer, cx);
2042                        workspace.open_project_item::<Editor>(active_pane, buffer, false, false, cx)
2043                    })
2044                    .log_err()?;
2045
2046                let (&excerpt_id, _, _) = editor
2047                    .read(cx)
2048                    .buffer()
2049                    .read(cx)
2050                    .read(cx)
2051                    .as_singleton()
2052                    .unwrap();
2053
2054                // Scroll the editor to the suggested assist
2055                editor.update(cx, |editor, cx| {
2056                    let multibuffer = editor.buffer().read(cx).snapshot(cx);
2057                    let (&excerpt_id, _, buffer) = multibuffer.as_singleton().unwrap();
2058                    let anchor = if group.context_range.start.to_offset(buffer) == 0 {
2059                        Anchor::min()
2060                    } else {
2061                        multibuffer
2062                            .anchor_in_excerpt(excerpt_id, group.context_range.start)
2063                            .unwrap()
2064                    };
2065
2066                    editor.set_scroll_anchor(
2067                        ScrollAnchor {
2068                            offset: gpui::Point::default(),
2069                            anchor,
2070                        },
2071                        cx,
2072                    );
2073                });
2074
2075                suggestion_groups.push((excerpt_id, group));
2076            } else {
2077                // If there are multiple buffers or suggestion groups, create a multibuffer
2078                let multibuffer = cx.new_model(|cx| {
2079                    let replica_id = self.project.read(cx).replica_id();
2080                    let mut multibuffer =
2081                        MultiBuffer::new(replica_id, Capability::ReadWrite).with_title(title);
2082                    for (buffer, groups) in suggestions {
2083                        let excerpt_ids = multibuffer.push_excerpts(
2084                            buffer,
2085                            groups.iter().map(|suggestion_group| ExcerptRange {
2086                                context: suggestion_group.context_range.clone(),
2087                                primary: None,
2088                            }),
2089                            cx,
2090                        );
2091                        suggestion_groups.extend(excerpt_ids.into_iter().zip(groups));
2092                    }
2093                    multibuffer
2094                });
2095
2096                editor = cx.new_view(|cx| {
2097                    Editor::for_multibuffer(multibuffer, Some(self.project.clone()), true, cx)
2098                });
2099                self.workspace
2100                    .update(cx, |workspace, cx| {
2101                        workspace.add_item_to_active_pane(Box::new(editor.clone()), None, false, cx)
2102                    })
2103                    .log_err()?;
2104            }
2105
2106            let mut assist_ids = Vec::new();
2107            for (excerpt_id, suggestion_group) in suggestion_groups {
2108                for suggestion in suggestion_group.suggestions {
2109                    assist_ids.extend(suggestion.show(
2110                        &editor,
2111                        excerpt_id,
2112                        &self.workspace,
2113                        &assistant_panel,
2114                        cx,
2115                    ));
2116                }
2117            }
2118
2119            if let Some(range) = self.active_workflow_step_range.clone() {
2120                self.workflow_assists.insert(
2121                    range,
2122                    WorkflowAssist {
2123                        assist_ids: assist_ids.clone(),
2124                        editor: editor.downgrade(),
2125                        editor_was_open,
2126                    },
2127                );
2128            }
2129
2130            Some((editor, assist_ids, editor_was_open))
2131        } {
2132            self.workflow_assists.insert(
2133                step_range.clone(),
2134                WorkflowAssist {
2135                    assist_ids,
2136                    editor_was_open,
2137                    editor: editor.downgrade(),
2138                },
2139            );
2140        }
2141
2142        self.active_workflow_step_range = Some(step_range);
2143
2144        Some(())
2145    }
2146
2147    fn active_workflow_step<'a>(&'a self, cx: &'a AppContext) -> Option<&'a crate::WorkflowStep> {
2148        self.active_workflow_step_range
2149            .as_ref()
2150            .and_then(|step_range| {
2151                self.context
2152                    .read(cx)
2153                    .workflow_step_for_range(step_range.clone())
2154            })
2155    }
2156
2157    fn workflow_step<'a>(
2158        &'a mut self,
2159        step_range: &Range<text::Anchor>,
2160        cx: &'a mut ViewContext<ContextEditor>,
2161    ) -> Option<&'a crate::WorkflowStep> {
2162        self.context
2163            .read(cx)
2164            .workflow_step_for_range(step_range.clone())
2165    }
2166
2167    fn scroll_to_existing_workflow_assist(
2168        &self,
2169        step_range: &Range<language::Anchor>,
2170        cx: &mut ViewContext<Self>,
2171    ) -> bool {
2172        let step_assists = match self.workflow_assists.get(step_range) {
2173            Some(assists) => assists,
2174            None => return false,
2175        };
2176        let editor = match step_assists.editor.upgrade() {
2177            Some(editor) => editor,
2178            None => return false,
2179        };
2180        for assist_id in &step_assists.assist_ids {
2181            match InlineAssistant::global(cx).status_for_assist(*assist_id, cx) {
2182                Some(CodegenStatus::Idle) | None => {}
2183                _ => {
2184                    self.workspace
2185                        .update(cx, |workspace, cx| {
2186                            workspace.activate_item(&editor, false, false, cx);
2187                        })
2188                        .ok();
2189                    InlineAssistant::update_global(cx, |assistant, cx| {
2190                        assistant.scroll_to_assist(*assist_id, cx)
2191                    });
2192                    return true;
2193                }
2194            }
2195        }
2196        false
2197    }
2198
2199    fn handle_editor_search_event(
2200        &mut self,
2201        _: View<Editor>,
2202        event: &SearchEvent,
2203        cx: &mut ViewContext<Self>,
2204    ) {
2205        cx.emit(event.clone());
2206    }
2207
2208    fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
2209        self.editor.update(cx, |editor, cx| {
2210            let snapshot = editor.snapshot(cx);
2211            let cursor = editor.selections.newest_anchor().head();
2212            let cursor_row = cursor
2213                .to_display_point(&snapshot.display_snapshot)
2214                .row()
2215                .as_f32();
2216            let scroll_position = editor
2217                .scroll_manager
2218                .anchor()
2219                .scroll_position(&snapshot.display_snapshot);
2220
2221            let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
2222            if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
2223                Some(ScrollPosition {
2224                    cursor,
2225                    offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
2226                })
2227            } else {
2228                None
2229            }
2230        })
2231    }
2232
2233    fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
2234        self.editor.update(cx, |editor, cx| {
2235            let buffer = editor.buffer().read(cx).snapshot(cx);
2236            let excerpt_id = *buffer.as_singleton().unwrap().0;
2237            let old_blocks = std::mem::take(&mut self.blocks);
2238            let new_blocks = self
2239                .context
2240                .read(cx)
2241                .messages(cx)
2242                .map(|message| BlockProperties {
2243                    position: buffer
2244                        .anchor_in_excerpt(excerpt_id, message.anchor)
2245                        .unwrap(),
2246                    height: 2,
2247                    style: BlockStyle::Sticky,
2248                    render: Box::new({
2249                        let context = self.context.clone();
2250                        move |cx| {
2251                            let message_id = message.id;
2252                            let sender = ButtonLike::new("role")
2253                                .style(ButtonStyle::Filled)
2254                                .child(match message.role {
2255                                    Role::User => Label::new("You").color(Color::Default),
2256                                    Role::Assistant => Label::new("Assistant").color(Color::Info),
2257                                    Role::System => Label::new("System").color(Color::Warning),
2258                                })
2259                                .tooltip(|cx| {
2260                                    Tooltip::with_meta(
2261                                        "Toggle message role",
2262                                        None,
2263                                        "Available roles: You (User), Assistant, System",
2264                                        cx,
2265                                    )
2266                                })
2267                                .on_click({
2268                                    let context = context.clone();
2269                                    move |_, cx| {
2270                                        context.update(cx, |context, cx| {
2271                                            context.cycle_message_roles(
2272                                                HashSet::from_iter(Some(message_id)),
2273                                                cx,
2274                                            )
2275                                        })
2276                                    }
2277                                });
2278
2279                            let trigger = Button::new("show-error", "Error")
2280                                .color(Color::Error)
2281                                .selected_label_color(Color::Error)
2282                                .selected_icon_color(Color::Error)
2283                                .icon(IconName::XCircle)
2284                                .icon_color(Color::Error)
2285                                .icon_size(IconSize::Small)
2286                                .icon_position(IconPosition::Start)
2287                                .tooltip(move |cx| {
2288                                    Tooltip::with_meta(
2289                                        "Error interacting with language model",
2290                                        None,
2291                                        "Click for more details",
2292                                        cx,
2293                                    )
2294                                });
2295                            h_flex()
2296                                .id(("message_header", message_id.as_u64()))
2297                                .pl(cx.gutter_dimensions.full_width())
2298                                .h_11()
2299                                .w_full()
2300                                .relative()
2301                                .gap_1()
2302                                .child(sender)
2303                                .children(
2304                                    if let MessageStatus::Error(error) = message.status.clone() {
2305                                        Some(
2306                                            PopoverMenu::new("show-error-popover")
2307                                                .menu(move |cx| {
2308                                                    Some(cx.new_view(|cx| ErrorPopover {
2309                                                        error: error.clone(),
2310                                                        focus_handle: cx.focus_handle(),
2311                                                    }))
2312                                                })
2313                                                .trigger(trigger),
2314                                        )
2315                                    } else {
2316                                        None
2317                                    },
2318                                )
2319                                .into_any_element()
2320                        }
2321                    }),
2322                    disposition: BlockDisposition::Above,
2323                })
2324                .collect::<Vec<_>>();
2325
2326            editor.remove_blocks(old_blocks, None, cx);
2327            let ids = editor.insert_blocks(new_blocks, None, cx);
2328            self.blocks = HashSet::from_iter(ids);
2329        });
2330    }
2331
2332    fn insert_selection(
2333        workspace: &mut Workspace,
2334        _: &InsertIntoEditor,
2335        cx: &mut ViewContext<Workspace>,
2336    ) {
2337        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2338            return;
2339        };
2340        let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
2341            return;
2342        };
2343        let Some(active_editor_view) = workspace
2344            .active_item(cx)
2345            .and_then(|item| item.act_as::<Editor>(cx))
2346        else {
2347            return;
2348        };
2349
2350        let context_editor = context_editor_view.read(cx).editor.read(cx);
2351        let anchor = context_editor.selections.newest_anchor();
2352        let text = context_editor
2353            .buffer()
2354            .read(cx)
2355            .read(cx)
2356            .text_for_range(anchor.range())
2357            .collect::<String>();
2358
2359        // If nothing is selected, don't delete the current selection; instead, be a no-op.
2360        if !text.is_empty() {
2361            active_editor_view.update(cx, |editor, cx| {
2362                editor.insert(&text, cx);
2363                editor.focus(cx);
2364            })
2365        }
2366    }
2367
2368    fn quote_selection(
2369        workspace: &mut Workspace,
2370        _: &QuoteSelection,
2371        cx: &mut ViewContext<Workspace>,
2372    ) {
2373        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2374            return;
2375        };
2376        let Some(editor) = workspace
2377            .active_item(cx)
2378            .and_then(|item| item.act_as::<Editor>(cx))
2379        else {
2380            return;
2381        };
2382
2383        let selection = editor.update(cx, |editor, cx| editor.selections.newest_adjusted(cx));
2384        let editor = editor.read(cx);
2385        let buffer = editor.buffer().read(cx).snapshot(cx);
2386        let range = editor::ToOffset::to_offset(&selection.start, &buffer)
2387            ..editor::ToOffset::to_offset(&selection.end, &buffer);
2388        let start_language = buffer.language_at(range.start);
2389        let end_language = buffer.language_at(range.end);
2390        let language_name = if start_language == end_language {
2391            start_language.map(|language| language.code_fence_block_name())
2392        } else {
2393            None
2394        };
2395        let language_name = language_name.as_deref().unwrap_or("");
2396
2397        let selected_text = buffer.text_for_range(range).collect::<String>();
2398        let text = if selected_text.is_empty() {
2399            None
2400        } else {
2401            Some(if language_name == "markdown" {
2402                selected_text
2403                    .lines()
2404                    .map(|line| format!("> {}", line))
2405                    .collect::<Vec<_>>()
2406                    .join("\n")
2407            } else {
2408                format!("```{language_name}\n{selected_text}\n```")
2409            })
2410        };
2411
2412        // Activate the panel
2413        if !panel.focus_handle(cx).contains_focused(cx) {
2414            workspace.toggle_panel_focus::<AssistantPanel>(cx);
2415        }
2416
2417        if let Some(text) = text {
2418            panel.update(cx, |_, cx| {
2419                // Wait to create a new context until the workspace is no longer
2420                // being updated.
2421                cx.defer(move |panel, cx| {
2422                    if let Some(context) = panel
2423                        .active_context_editor(cx)
2424                        .or_else(|| panel.new_context(cx))
2425                    {
2426                        context.update(cx, |context, cx| {
2427                            context
2428                                .editor
2429                                .update(cx, |editor, cx| editor.insert(&text, cx))
2430                        });
2431                    };
2432                });
2433            });
2434        }
2435    }
2436
2437    fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
2438        let editor = self.editor.read(cx);
2439        let context = self.context.read(cx);
2440        if editor.selections.count() == 1 {
2441            let selection = editor.selections.newest::<usize>(cx);
2442            let mut copied_text = String::new();
2443            let mut spanned_messages = 0;
2444            for message in context.messages(cx) {
2445                if message.offset_range.start >= selection.range().end {
2446                    break;
2447                } else if message.offset_range.end >= selection.range().start {
2448                    let range = cmp::max(message.offset_range.start, selection.range().start)
2449                        ..cmp::min(message.offset_range.end, selection.range().end);
2450                    if !range.is_empty() {
2451                        spanned_messages += 1;
2452                        write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
2453                        for chunk in context.buffer().read(cx).text_for_range(range) {
2454                            copied_text.push_str(chunk);
2455                        }
2456                        copied_text.push('\n');
2457                    }
2458                }
2459            }
2460
2461            if spanned_messages > 1 {
2462                cx.write_to_clipboard(ClipboardItem::new(copied_text));
2463                return;
2464            }
2465        }
2466
2467        cx.propagate();
2468    }
2469
2470    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
2471        self.context.update(cx, |context, cx| {
2472            let selections = self.editor.read(cx).selections.disjoint_anchors();
2473            for selection in selections.as_ref() {
2474                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
2475                let range = selection
2476                    .map(|endpoint| endpoint.to_offset(&buffer))
2477                    .range();
2478                context.split_message(range, cx);
2479            }
2480        });
2481    }
2482
2483    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
2484        self.context.update(cx, |context, cx| {
2485            context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx)
2486        });
2487    }
2488
2489    fn title(&self, cx: &AppContext) -> Cow<str> {
2490        self.context
2491            .read(cx)
2492            .summary()
2493            .map(|summary| summary.text.clone())
2494            .map(Cow::Owned)
2495            .unwrap_or_else(|| Cow::Borrowed(DEFAULT_TAB_TITLE))
2496    }
2497
2498    fn render_notice(&self, cx: &mut ViewContext<Self>) -> Option<AnyElement> {
2499        use feature_flags::FeatureFlagAppExt;
2500        let nudge = self.assistant_panel.upgrade().map(|assistant_panel| {
2501            assistant_panel.read(cx).show_zed_ai_notice && cx.has_flag::<feature_flags::ZedPro>()
2502        });
2503
2504        if nudge.map_or(false, |value| value) {
2505            Some(
2506                h_flex()
2507                    .p_3()
2508                    .border_b_1()
2509                    .border_color(cx.theme().colors().border_variant)
2510                    .bg(cx.theme().colors().editor_background)
2511                    .justify_between()
2512                    .child(
2513                        h_flex()
2514                            .gap_3()
2515                            .child(Icon::new(IconName::ZedAssistant).color(Color::Accent))
2516                            .child(Label::new("Zed AI is here! Get started by signing in →")),
2517                    )
2518                    .child(
2519                        Button::new("sign-in", "Sign in")
2520                            .size(ButtonSize::Compact)
2521                            .style(ButtonStyle::Filled)
2522                            .on_click(cx.listener(|this, _event, cx| {
2523                                let client = this
2524                                    .workspace
2525                                    .update(cx, |workspace, _| workspace.client().clone())
2526                                    .log_err();
2527
2528                                if let Some(client) = client {
2529                                    cx.spawn(|this, mut cx| async move {
2530                                        client.authenticate_and_connect(true, &mut cx).await?;
2531                                        this.update(&mut cx, |_, cx| cx.notify())
2532                                    })
2533                                    .detach_and_log_err(cx)
2534                                }
2535                            })),
2536                    )
2537                    .into_any_element(),
2538            )
2539        } else if let Some(configuration_error) = configuration_error(cx) {
2540            let label = match configuration_error {
2541                ConfigurationError::NoProvider => "No LLM provider selected.",
2542                ConfigurationError::ProviderNotAuthenticated => "LLM provider is not configured.",
2543            };
2544            Some(
2545                h_flex()
2546                    .p_3()
2547                    .border_b_1()
2548                    .border_color(cx.theme().colors().border_variant)
2549                    .bg(cx.theme().colors().editor_background)
2550                    .justify_between()
2551                    .child(
2552                        h_flex()
2553                            .gap_3()
2554                            .child(
2555                                Icon::new(IconName::ExclamationTriangle)
2556                                    .size(IconSize::Small)
2557                                    .color(Color::Warning),
2558                            )
2559                            .child(Label::new(label)),
2560                    )
2561                    .child(
2562                        Button::new("open-configuration", "Open configuration")
2563                            .size(ButtonSize::Compact)
2564                            .icon_size(IconSize::Small)
2565                            .style(ButtonStyle::Filled)
2566                            .on_click({
2567                                let focus_handle = self.focus_handle(cx).clone();
2568                                move |_event, cx| {
2569                                    focus_handle.dispatch_action(&ShowConfiguration, cx);
2570                                }
2571                            }),
2572                    )
2573                    .into_any_element(),
2574            )
2575        } else {
2576            None
2577        }
2578    }
2579
2580    fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
2581        let focus_handle = self.focus_handle(cx).clone();
2582        let button_text = match self.active_workflow_step(cx) {
2583            Some(step) => {
2584                if step.status.is_resolved() {
2585                    "Apply Changes"
2586                } else {
2587                    "Computing Changes..."
2588                }
2589            }
2590            None => "Send",
2591        };
2592
2593        let (style, tooltip) = match token_state(&self.context, cx) {
2594            Some(TokenState::NoTokensLeft { .. }) => (
2595                ButtonStyle::Tinted(TintColor::Negative),
2596                Some(Tooltip::text("Token limit reached", cx)),
2597            ),
2598            Some(TokenState::HasMoreTokens {
2599                over_warn_threshold,
2600                ..
2601            }) => {
2602                let (style, tooltip) = if over_warn_threshold {
2603                    (
2604                        ButtonStyle::Tinted(TintColor::Warning),
2605                        Some(Tooltip::text("Token limit is close to exhaustion", cx)),
2606                    )
2607                } else {
2608                    (ButtonStyle::Filled, None)
2609                };
2610                (style, tooltip)
2611            }
2612            None => (ButtonStyle::Filled, None),
2613        };
2614
2615        ButtonLike::new("send_button")
2616            .style(style)
2617            .when_some(tooltip, |button, tooltip| {
2618                button.tooltip(move |_| tooltip.clone())
2619            })
2620            .layer(ElevationIndex::ModalSurface)
2621            .children(
2622                KeyBinding::for_action_in(&Assist, &focus_handle, cx)
2623                    .map(|binding| binding.into_any_element()),
2624            )
2625            .child(Label::new(button_text))
2626            .on_click(move |_event, cx| {
2627                focus_handle.dispatch_action(&Assist, cx);
2628            })
2629    }
2630
2631    fn workflow_step_range_for_cursor(&self, cx: &AppContext) -> Option<Range<language::Anchor>> {
2632        let newest_cursor = self
2633            .editor
2634            .read(cx)
2635            .selections
2636            .newest_anchor()
2637            .head()
2638            .text_anchor;
2639        let context = self.context.read(cx);
2640        let buffer = context.buffer().read(cx);
2641
2642        let edit_steps = context.workflow_steps();
2643        edit_steps
2644            .binary_search_by(|step| {
2645                let step_range = step.tagged_range.clone();
2646                if newest_cursor.cmp(&step_range.start, buffer).is_lt() {
2647                    Ordering::Greater
2648                } else if newest_cursor.cmp(&step_range.end, buffer).is_gt() {
2649                    Ordering::Less
2650                } else {
2651                    Ordering::Equal
2652                }
2653            })
2654            .ok()
2655            .map(|index| edit_steps[index].tagged_range.clone())
2656    }
2657}
2658
2659impl EventEmitter<EditorEvent> for ContextEditor {}
2660impl EventEmitter<SearchEvent> for ContextEditor {}
2661
2662impl Render for ContextEditor {
2663    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
2664        v_flex()
2665            .key_context("ContextEditor")
2666            .capture_action(cx.listener(ContextEditor::cancel_last_assist))
2667            .capture_action(cx.listener(ContextEditor::save))
2668            .capture_action(cx.listener(ContextEditor::copy))
2669            .capture_action(cx.listener(ContextEditor::cycle_message_role))
2670            .capture_action(cx.listener(ContextEditor::confirm_command))
2671            .on_action(cx.listener(ContextEditor::assist))
2672            .on_action(cx.listener(ContextEditor::split))
2673            .on_action(cx.listener(ContextEditor::debug_edit_steps))
2674            .size_full()
2675            .children(self.render_notice(cx))
2676            .child(
2677                div()
2678                    .flex_grow()
2679                    .bg(cx.theme().colors().editor_background)
2680                    .child(self.editor.clone()),
2681            )
2682            .child(
2683                h_flex().flex_none().relative().child(
2684                    h_flex()
2685                        .w_full()
2686                        .absolute()
2687                        .right_4()
2688                        .bottom_2()
2689                        .justify_end()
2690                        .child(self.render_send_button(cx)),
2691                ),
2692            )
2693    }
2694}
2695
2696struct ErrorPopover {
2697    error: SharedString,
2698    focus_handle: FocusHandle,
2699}
2700
2701impl EventEmitter<DismissEvent> for ErrorPopover {}
2702
2703impl FocusableView for ErrorPopover {
2704    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
2705        self.focus_handle.clone()
2706    }
2707}
2708
2709impl Render for ErrorPopover {
2710    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
2711        v_flex()
2712            .mt_2()
2713            .max_w_96()
2714            .py_2()
2715            .px_3()
2716            .gap_0p5()
2717            .elevation_2(cx)
2718            .bg(cx.theme().colors().surface_background)
2719            .occlude()
2720            .child(Label::new("Error interacting with language model").weight(FontWeight::SEMIBOLD))
2721            .child(Label::new(self.error.clone()))
2722            .child(
2723                h_flex().justify_end().mt_1().child(
2724                    Button::new("dismiss", "Dismiss")
2725                        .on_click(cx.listener(|_, _, cx| cx.emit(DismissEvent))),
2726                ),
2727            )
2728    }
2729}
2730
2731impl FocusableView for ContextEditor {
2732    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
2733        self.editor.focus_handle(cx)
2734    }
2735}
2736
2737impl Item for ContextEditor {
2738    type Event = editor::EditorEvent;
2739
2740    fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
2741        Some(util::truncate_and_trailoff(&self.title(cx), MAX_TAB_TITLE_LEN).into())
2742    }
2743
2744    fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
2745        match event {
2746            EditorEvent::Edited { .. } => {
2747                f(item::ItemEvent::Edit);
2748            }
2749            EditorEvent::TitleChanged => {
2750                f(item::ItemEvent::UpdateTab);
2751            }
2752            _ => {}
2753        }
2754    }
2755
2756    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
2757        Some(self.title(cx).to_string().into())
2758    }
2759
2760    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
2761        Some(Box::new(handle.clone()))
2762    }
2763
2764    fn set_nav_history(&mut self, nav_history: pane::ItemNavHistory, cx: &mut ViewContext<Self>) {
2765        self.editor.update(cx, |editor, cx| {
2766            Item::set_nav_history(editor, nav_history, cx)
2767        })
2768    }
2769
2770    fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
2771        self.editor
2772            .update(cx, |editor, cx| Item::navigate(editor, data, cx))
2773    }
2774
2775    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
2776        self.editor
2777            .update(cx, |editor, cx| Item::deactivated(editor, cx))
2778    }
2779}
2780
2781impl SearchableItem for ContextEditor {
2782    type Match = <Editor as SearchableItem>::Match;
2783
2784    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
2785        self.editor.update(cx, |editor, cx| {
2786            editor.clear_matches(cx);
2787        });
2788    }
2789
2790    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
2791        self.editor
2792            .update(cx, |editor, cx| editor.update_matches(matches, cx));
2793    }
2794
2795    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
2796        self.editor
2797            .update(cx, |editor, cx| editor.query_suggestion(cx))
2798    }
2799
2800    fn activate_match(
2801        &mut self,
2802        index: usize,
2803        matches: &[Self::Match],
2804        cx: &mut ViewContext<Self>,
2805    ) {
2806        self.editor.update(cx, |editor, cx| {
2807            editor.activate_match(index, matches, cx);
2808        });
2809    }
2810
2811    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
2812        self.editor
2813            .update(cx, |editor, cx| editor.select_matches(matches, cx));
2814    }
2815
2816    fn replace(
2817        &mut self,
2818        identifier: &Self::Match,
2819        query: &project::search::SearchQuery,
2820        cx: &mut ViewContext<Self>,
2821    ) {
2822        self.editor
2823            .update(cx, |editor, cx| editor.replace(identifier, query, cx));
2824    }
2825
2826    fn find_matches(
2827        &mut self,
2828        query: Arc<project::search::SearchQuery>,
2829        cx: &mut ViewContext<Self>,
2830    ) -> Task<Vec<Self::Match>> {
2831        self.editor
2832            .update(cx, |editor, cx| editor.find_matches(query, cx))
2833    }
2834
2835    fn active_match_index(
2836        &mut self,
2837        matches: &[Self::Match],
2838        cx: &mut ViewContext<Self>,
2839    ) -> Option<usize> {
2840        self.editor
2841            .update(cx, |editor, cx| editor.active_match_index(matches, cx))
2842    }
2843}
2844
2845impl FollowableItem for ContextEditor {
2846    fn remote_id(&self) -> Option<workspace::ViewId> {
2847        self.remote_id
2848    }
2849
2850    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
2851        let context = self.context.read(cx);
2852        Some(proto::view::Variant::ContextEditor(
2853            proto::view::ContextEditor {
2854                context_id: context.id().to_proto(),
2855                editor: if let Some(proto::view::Variant::Editor(proto)) =
2856                    self.editor.read(cx).to_state_proto(cx)
2857                {
2858                    Some(proto)
2859                } else {
2860                    None
2861                },
2862            },
2863        ))
2864    }
2865
2866    fn from_state_proto(
2867        workspace: View<Workspace>,
2868        id: workspace::ViewId,
2869        state: &mut Option<proto::view::Variant>,
2870        cx: &mut WindowContext,
2871    ) -> Option<Task<Result<View<Self>>>> {
2872        let proto::view::Variant::ContextEditor(_) = state.as_ref()? else {
2873            return None;
2874        };
2875        let Some(proto::view::Variant::ContextEditor(state)) = state.take() else {
2876            unreachable!()
2877        };
2878
2879        let context_id = ContextId::from_proto(state.context_id);
2880        let editor_state = state.editor?;
2881
2882        let (project, panel) = workspace.update(cx, |workspace, cx| {
2883            Some((
2884                workspace.project().clone(),
2885                workspace.panel::<AssistantPanel>(cx)?,
2886            ))
2887        })?;
2888
2889        let context_editor =
2890            panel.update(cx, |panel, cx| panel.open_remote_context(context_id, cx));
2891
2892        Some(cx.spawn(|mut cx| async move {
2893            let context_editor = context_editor.await?;
2894            context_editor
2895                .update(&mut cx, |context_editor, cx| {
2896                    context_editor.remote_id = Some(id);
2897                    context_editor.editor.update(cx, |editor, cx| {
2898                        editor.apply_update_proto(
2899                            &project,
2900                            proto::update_view::Variant::Editor(proto::update_view::Editor {
2901                                selections: editor_state.selections,
2902                                pending_selection: editor_state.pending_selection,
2903                                scroll_top_anchor: editor_state.scroll_top_anchor,
2904                                scroll_x: editor_state.scroll_y,
2905                                scroll_y: editor_state.scroll_y,
2906                                ..Default::default()
2907                            }),
2908                            cx,
2909                        )
2910                    })
2911                })?
2912                .await?;
2913            Ok(context_editor)
2914        }))
2915    }
2916
2917    fn to_follow_event(event: &Self::Event) -> Option<item::FollowEvent> {
2918        Editor::to_follow_event(event)
2919    }
2920
2921    fn add_event_to_update_proto(
2922        &self,
2923        event: &Self::Event,
2924        update: &mut Option<proto::update_view::Variant>,
2925        cx: &WindowContext,
2926    ) -> bool {
2927        self.editor
2928            .read(cx)
2929            .add_event_to_update_proto(event, update, cx)
2930    }
2931
2932    fn apply_update_proto(
2933        &mut self,
2934        project: &Model<Project>,
2935        message: proto::update_view::Variant,
2936        cx: &mut ViewContext<Self>,
2937    ) -> Task<Result<()>> {
2938        self.editor.update(cx, |editor, cx| {
2939            editor.apply_update_proto(project, message, cx)
2940        })
2941    }
2942
2943    fn is_project_item(&self, _cx: &WindowContext) -> bool {
2944        true
2945    }
2946
2947    fn set_leader_peer_id(
2948        &mut self,
2949        leader_peer_id: Option<proto::PeerId>,
2950        cx: &mut ViewContext<Self>,
2951    ) {
2952        self.editor.update(cx, |editor, cx| {
2953            editor.set_leader_peer_id(leader_peer_id, cx)
2954        })
2955    }
2956
2957    fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<item::Dedup> {
2958        if existing.context.read(cx).id() == self.context.read(cx).id() {
2959            Some(item::Dedup::KeepExisting)
2960        } else {
2961            None
2962        }
2963    }
2964}
2965
2966pub struct ContextEditorToolbarItem {
2967    fs: Arc<dyn Fs>,
2968    workspace: WeakView<Workspace>,
2969    active_context_editor: Option<WeakView<ContextEditor>>,
2970    model_summary_editor: View<Editor>,
2971}
2972
2973impl ContextEditorToolbarItem {
2974    pub fn new(
2975        workspace: &Workspace,
2976        _model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
2977        model_summary_editor: View<Editor>,
2978    ) -> Self {
2979        Self {
2980            fs: workspace.app_state().fs.clone(),
2981            workspace: workspace.weak_handle(),
2982            active_context_editor: None,
2983            model_summary_editor,
2984        }
2985    }
2986
2987    fn render_inject_context_menu(&self, cx: &mut ViewContext<Self>) -> impl Element {
2988        let commands = SlashCommandRegistry::global(cx);
2989        let active_editor_focus_handle = self.workspace.upgrade().and_then(|workspace| {
2990            Some(
2991                workspace
2992                    .read(cx)
2993                    .active_item_as::<Editor>(cx)?
2994                    .focus_handle(cx),
2995            )
2996        });
2997        let active_context_editor = self.active_context_editor.clone();
2998
2999        PopoverMenu::new("inject-context-menu")
3000            .trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
3001                Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
3002            }))
3003            .menu(move |cx| {
3004                let active_context_editor = active_context_editor.clone()?;
3005                ContextMenu::build(cx, |mut menu, _cx| {
3006                    for command_name in commands.featured_command_names() {
3007                        if let Some(command) = commands.command(&command_name) {
3008                            let menu_text = SharedString::from(Arc::from(command.menu_text()));
3009                            menu = menu.custom_entry(
3010                                {
3011                                    let command_name = command_name.clone();
3012                                    move |_cx| {
3013                                        h_flex()
3014                                            .gap_4()
3015                                            .w_full()
3016                                            .justify_between()
3017                                            .child(Label::new(menu_text.clone()))
3018                                            .child(
3019                                                Label::new(format!("/{command_name}"))
3020                                                    .color(Color::Muted),
3021                                            )
3022                                            .into_any()
3023                                    }
3024                                },
3025                                {
3026                                    let active_context_editor = active_context_editor.clone();
3027                                    move |cx| {
3028                                        active_context_editor
3029                                            .update(cx, |context_editor, cx| {
3030                                                context_editor.insert_command(&command_name, cx)
3031                                            })
3032                                            .ok();
3033                                    }
3034                                },
3035                            )
3036                        }
3037                    }
3038
3039                    if let Some(active_editor_focus_handle) = active_editor_focus_handle.clone() {
3040                        menu = menu
3041                            .context(active_editor_focus_handle)
3042                            .action("Quote Selection", Box::new(QuoteSelection));
3043                    }
3044
3045                    menu
3046                })
3047                .into()
3048            })
3049    }
3050
3051    fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
3052        let context = &self
3053            .active_context_editor
3054            .as_ref()?
3055            .upgrade()?
3056            .read(cx)
3057            .context;
3058        let (token_count_color, token_count, max_token_count) = match token_state(context, cx)? {
3059            TokenState::NoTokensLeft {
3060                max_token_count,
3061                token_count,
3062            } => (Color::Error, token_count, max_token_count),
3063            TokenState::HasMoreTokens {
3064                max_token_count,
3065                token_count,
3066                over_warn_threshold,
3067            } => {
3068                let color = if over_warn_threshold {
3069                    Color::Warning
3070                } else {
3071                    Color::Muted
3072                };
3073                (color, token_count, max_token_count)
3074            }
3075        };
3076        Some(
3077            h_flex()
3078                .gap_0p5()
3079                .child(
3080                    Label::new(humanize_token_count(token_count))
3081                        .size(LabelSize::Small)
3082                        .color(token_count_color),
3083                )
3084                .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
3085                .child(
3086                    Label::new(humanize_token_count(max_token_count))
3087                        .size(LabelSize::Small)
3088                        .color(Color::Muted),
3089                ),
3090        )
3091    }
3092}
3093
3094impl Render for ContextEditorToolbarItem {
3095    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3096        let left_side = h_flex()
3097            .gap_2()
3098            .flex_1()
3099            .min_w(rems(DEFAULT_TAB_TITLE.len() as f32))
3100            .when(self.active_context_editor.is_some(), |left_side| {
3101                left_side
3102                    .child(
3103                        IconButton::new("regenerate-context", IconName::ArrowCircle)
3104                            .visible_on_hover("toolbar")
3105                            .tooltip(|cx| Tooltip::text("Regenerate Summary", cx))
3106                            .on_click(cx.listener(move |_, _, cx| {
3107                                cx.emit(ContextEditorToolbarItemEvent::RegenerateSummary)
3108                            })),
3109                    )
3110                    .child(self.model_summary_editor.clone())
3111            });
3112
3113        let active_provider = LanguageModelRegistry::read_global(cx).active_provider();
3114        let active_model = LanguageModelRegistry::read_global(cx).active_model();
3115
3116        let right_side = h_flex()
3117            .gap_2()
3118            .child(ModelSelector::new(
3119                self.fs.clone(),
3120                ButtonLike::new("active-model")
3121                    .style(ButtonStyle::Subtle)
3122                    .child(
3123                        h_flex()
3124                            .w_full()
3125                            .gap_0p5()
3126                            .child(
3127                                div()
3128                                    .overflow_x_hidden()
3129                                    .flex_grow()
3130                                    .whitespace_nowrap()
3131                                    .child(match (active_provider, active_model) {
3132                                        (Some(provider), Some(model)) => h_flex()
3133                                            .gap_1()
3134                                            .child(
3135                                                Icon::new(provider.icon())
3136                                                    .color(Color::Muted)
3137                                                    .size(IconSize::XSmall),
3138                                            )
3139                                            .child(
3140                                                Label::new(model.name().0)
3141                                                    .size(LabelSize::Small)
3142                                                    .color(Color::Muted),
3143                                            )
3144                                            .into_any_element(),
3145                                        _ => Label::new("No model selected")
3146                                            .size(LabelSize::Small)
3147                                            .color(Color::Muted)
3148                                            .into_any_element(),
3149                                    }),
3150                            )
3151                            .child(
3152                                Icon::new(IconName::ChevronDown)
3153                                    .color(Color::Muted)
3154                                    .size(IconSize::XSmall),
3155                            ),
3156                    )
3157                    .tooltip(move |cx| {
3158                        Tooltip::for_action("Change Model", &ToggleModelSelector, cx)
3159                    }),
3160            ))
3161            .children(self.render_remaining_tokens(cx))
3162            .child(self.render_inject_context_menu(cx));
3163
3164        h_flex()
3165            .size_full()
3166            .justify_between()
3167            .child(left_side)
3168            .child(right_side)
3169    }
3170}
3171
3172impl ToolbarItemView for ContextEditorToolbarItem {
3173    fn set_active_pane_item(
3174        &mut self,
3175        active_pane_item: Option<&dyn ItemHandle>,
3176        cx: &mut ViewContext<Self>,
3177    ) -> ToolbarItemLocation {
3178        self.active_context_editor = active_pane_item
3179            .and_then(|item| item.act_as::<ContextEditor>(cx))
3180            .map(|editor| editor.downgrade());
3181        cx.notify();
3182        if self.active_context_editor.is_none() {
3183            ToolbarItemLocation::Hidden
3184        } else {
3185            ToolbarItemLocation::PrimaryRight
3186        }
3187    }
3188
3189    fn pane_focus_update(&mut self, _pane_focused: bool, cx: &mut ViewContext<Self>) {
3190        cx.notify();
3191    }
3192}
3193
3194impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
3195
3196enum ContextEditorToolbarItemEvent {
3197    RegenerateSummary,
3198}
3199impl EventEmitter<ContextEditorToolbarItemEvent> for ContextEditorToolbarItem {}
3200
3201pub struct ContextHistory {
3202    picker: View<Picker<SavedContextPickerDelegate>>,
3203    _subscriptions: Vec<Subscription>,
3204    assistant_panel: WeakView<AssistantPanel>,
3205}
3206
3207impl ContextHistory {
3208    fn new(
3209        project: Model<Project>,
3210        context_store: Model<ContextStore>,
3211        assistant_panel: WeakView<AssistantPanel>,
3212        cx: &mut ViewContext<Self>,
3213    ) -> Self {
3214        let picker = cx.new_view(|cx| {
3215            Picker::uniform_list(
3216                SavedContextPickerDelegate::new(project, context_store.clone()),
3217                cx,
3218            )
3219            .modal(false)
3220            .max_height(None)
3221        });
3222
3223        let _subscriptions = vec![
3224            cx.observe(&context_store, |this, _, cx| {
3225                this.picker.update(cx, |picker, cx| picker.refresh(cx));
3226            }),
3227            cx.subscribe(&picker, Self::handle_picker_event),
3228        ];
3229
3230        Self {
3231            picker,
3232            _subscriptions,
3233            assistant_panel,
3234        }
3235    }
3236
3237    fn handle_picker_event(
3238        &mut self,
3239        _: View<Picker<SavedContextPickerDelegate>>,
3240        event: &SavedContextPickerEvent,
3241        cx: &mut ViewContext<Self>,
3242    ) {
3243        let SavedContextPickerEvent::Confirmed(context) = event;
3244        self.assistant_panel
3245            .update(cx, |assistant_panel, cx| match context {
3246                ContextMetadata::Remote(metadata) => {
3247                    assistant_panel
3248                        .open_remote_context(metadata.id.clone(), cx)
3249                        .detach_and_log_err(cx);
3250                }
3251                ContextMetadata::Saved(metadata) => {
3252                    assistant_panel
3253                        .open_saved_context(metadata.path.clone(), cx)
3254                        .detach_and_log_err(cx);
3255                }
3256            })
3257            .ok();
3258    }
3259}
3260
3261impl Render for ContextHistory {
3262    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
3263        div().size_full().child(self.picker.clone())
3264    }
3265}
3266
3267impl FocusableView for ContextHistory {
3268    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3269        self.picker.focus_handle(cx)
3270    }
3271}
3272
3273impl EventEmitter<()> for ContextHistory {}
3274
3275impl Item for ContextHistory {
3276    type Event = ();
3277
3278    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
3279        Some("History".into())
3280    }
3281}
3282
3283pub struct ConfigurationView {
3284    focus_handle: FocusHandle,
3285    configuration_views: HashMap<LanguageModelProviderId, AnyView>,
3286    _registry_subscription: Subscription,
3287}
3288
3289impl ConfigurationView {
3290    fn new(cx: &mut ViewContext<Self>) -> Self {
3291        let focus_handle = cx.focus_handle();
3292
3293        let registry_subscription = cx.subscribe(
3294            &LanguageModelRegistry::global(cx),
3295            |this, _, event: &language_model::Event, cx| match event {
3296                language_model::Event::AddedProvider(provider_id) => {
3297                    let provider = LanguageModelRegistry::read_global(cx).provider(provider_id);
3298                    if let Some(provider) = provider {
3299                        this.add_configuration_view(&provider, cx);
3300                    }
3301                }
3302                language_model::Event::RemovedProvider(provider_id) => {
3303                    this.remove_configuration_view(provider_id);
3304                }
3305                _ => {}
3306            },
3307        );
3308
3309        let mut this = Self {
3310            focus_handle,
3311            configuration_views: HashMap::default(),
3312            _registry_subscription: registry_subscription,
3313        };
3314        this.build_configuration_views(cx);
3315        this
3316    }
3317
3318    fn build_configuration_views(&mut self, cx: &mut ViewContext<Self>) {
3319        let providers = LanguageModelRegistry::read_global(cx).providers();
3320        for provider in providers {
3321            self.add_configuration_view(&provider, cx);
3322        }
3323    }
3324
3325    fn remove_configuration_view(&mut self, provider_id: &LanguageModelProviderId) {
3326        self.configuration_views.remove(provider_id);
3327    }
3328
3329    fn add_configuration_view(
3330        &mut self,
3331        provider: &Arc<dyn LanguageModelProvider>,
3332        cx: &mut ViewContext<Self>,
3333    ) {
3334        let configuration_view = provider.configuration_view(cx);
3335        self.configuration_views
3336            .insert(provider.id(), configuration_view);
3337    }
3338
3339    fn render_provider_view(
3340        &mut self,
3341        provider: &Arc<dyn LanguageModelProvider>,
3342        cx: &mut ViewContext<Self>,
3343    ) -> Div {
3344        let provider_name = provider.name().0.clone();
3345        let configuration_view = self.configuration_views.get(&provider.id()).cloned();
3346
3347        let open_new_context = cx.listener({
3348            let provider = provider.clone();
3349            move |_, _, cx| {
3350                cx.emit(ConfigurationViewEvent::NewProviderContextEditor(
3351                    provider.clone(),
3352                ))
3353            }
3354        });
3355
3356        v_flex()
3357            .gap_2()
3358            .child(
3359                h_flex()
3360                    .justify_between()
3361                    .child(Headline::new(provider_name.clone()).size(HeadlineSize::Small))
3362                    .when(provider.is_authenticated(cx), move |this| {
3363                        this.child(
3364                            h_flex().justify_end().child(
3365                                Button::new("new-context", "Open new context")
3366                                    .icon_position(IconPosition::Start)
3367                                    .icon(IconName::Plus)
3368                                    .style(ButtonStyle::Filled)
3369                                    .layer(ElevationIndex::ModalSurface)
3370                                    .on_click(open_new_context),
3371                            ),
3372                        )
3373                    }),
3374            )
3375            .child(
3376                div()
3377                    .p(Spacing::Large.rems(cx))
3378                    .bg(cx.theme().colors().surface_background)
3379                    .border_1()
3380                    .border_color(cx.theme().colors().border_variant)
3381                    .rounded_md()
3382                    .when(configuration_view.is_none(), |this| {
3383                        this.child(div().child(Label::new(format!(
3384                            "No configuration view for {}",
3385                            provider_name
3386                        ))))
3387                    })
3388                    .when_some(configuration_view, |this, configuration_view| {
3389                        this.child(configuration_view)
3390                    }),
3391            )
3392    }
3393}
3394
3395impl Render for ConfigurationView {
3396    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3397        let providers = LanguageModelRegistry::read_global(cx).providers();
3398        let provider_views = providers
3399            .into_iter()
3400            .map(|provider| self.render_provider_view(&provider, cx))
3401            .collect::<Vec<_>>();
3402
3403        v_flex()
3404            .id("assistant-configuration-view")
3405            .track_focus(&self.focus_handle)
3406            .bg(cx.theme().colors().editor_background)
3407            .size_full()
3408            .overflow_y_scroll()
3409            .child(
3410                v_flex()
3411                    .p(Spacing::XXLarge.rems(cx))
3412                    .border_b_1()
3413                    .border_color(cx.theme().colors().border)
3414                    .gap_1()
3415                    .child(Headline::new("Configure your Assistant").size(HeadlineSize::Medium))
3416                    .child(
3417                        Label::new(
3418                            "At least one LLM provider must be configured to use the Assistant.",
3419                        )
3420                        .color(Color::Muted),
3421                    ),
3422            )
3423            .child(
3424                v_flex()
3425                    .p(Spacing::XXLarge.rems(cx))
3426                    .mt_1()
3427                    .gap_6()
3428                    .size_full()
3429                    .children(provider_views),
3430            )
3431    }
3432}
3433
3434pub enum ConfigurationViewEvent {
3435    NewProviderContextEditor(Arc<dyn LanguageModelProvider>),
3436}
3437
3438impl EventEmitter<ConfigurationViewEvent> for ConfigurationView {}
3439
3440impl FocusableView for ConfigurationView {
3441    fn focus_handle(&self, _: &AppContext) -> FocusHandle {
3442        self.focus_handle.clone()
3443    }
3444}
3445
3446impl Item for ConfigurationView {
3447    type Event = ConfigurationViewEvent;
3448
3449    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
3450        Some("Configuration".into())
3451    }
3452}
3453
3454type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
3455
3456fn render_slash_command_output_toggle(
3457    row: MultiBufferRow,
3458    is_folded: bool,
3459    fold: ToggleFold,
3460    _cx: &mut WindowContext,
3461) -> AnyElement {
3462    Disclosure::new(
3463        ("slash-command-output-fold-indicator", row.0 as u64),
3464        !is_folded,
3465    )
3466    .selected(is_folded)
3467    .on_click(move |_e, cx| fold(!is_folded, cx))
3468    .into_any_element()
3469}
3470
3471fn render_pending_slash_command_gutter_decoration(
3472    row: MultiBufferRow,
3473    status: &PendingSlashCommandStatus,
3474    confirm_command: Arc<dyn Fn(&mut WindowContext)>,
3475) -> AnyElement {
3476    let mut icon = IconButton::new(
3477        ("slash-command-gutter-decoration", row.0),
3478        ui::IconName::TriangleRight,
3479    )
3480    .on_click(move |_e, cx| confirm_command(cx))
3481    .icon_size(ui::IconSize::Small)
3482    .size(ui::ButtonSize::None);
3483
3484    match status {
3485        PendingSlashCommandStatus::Idle => {
3486            icon = icon.icon_color(Color::Muted);
3487        }
3488        PendingSlashCommandStatus::Running { .. } => {
3489            icon = icon.selected(true);
3490        }
3491        PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
3492    }
3493
3494    icon.into_any_element()
3495}
3496
3497fn render_docs_slash_command_trailer(
3498    row: MultiBufferRow,
3499    command: PendingSlashCommand,
3500    cx: &mut WindowContext,
3501) -> AnyElement {
3502    let Some(argument) = command.argument else {
3503        return Empty.into_any();
3504    };
3505
3506    let args = DocsSlashCommandArgs::parse(&argument);
3507
3508    let Some(store) = args
3509        .provider()
3510        .and_then(|provider| IndexedDocsStore::try_global(provider, cx).ok())
3511    else {
3512        return Empty.into_any();
3513    };
3514
3515    let Some(package) = args.package() else {
3516        return Empty.into_any();
3517    };
3518
3519    let mut children = Vec::new();
3520
3521    if store.is_indexing(&package) {
3522        children.push(
3523            div()
3524                .id(("crates-being-indexed", row.0))
3525                .child(Icon::new(IconName::ArrowCircle).with_animation(
3526                    "arrow-circle",
3527                    Animation::new(Duration::from_secs(4)).repeat(),
3528                    |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
3529                ))
3530                .tooltip({
3531                    let package = package.clone();
3532                    move |cx| Tooltip::text(format!("Indexing {package}"), cx)
3533                })
3534                .into_any_element(),
3535        );
3536    }
3537
3538    if let Some(latest_error) = store.latest_error_for_package(&package) {
3539        children.push(
3540            div()
3541                .id(("latest-error", row.0))
3542                .child(
3543                    Icon::new(IconName::ExclamationTriangle)
3544                        .size(IconSize::Small)
3545                        .color(Color::Warning),
3546                )
3547                .tooltip(move |cx| Tooltip::text(format!("Failed to index: {latest_error}"), cx))
3548                .into_any_element(),
3549        )
3550    }
3551
3552    let is_indexing = store.is_indexing(&package);
3553    let latest_error = store.latest_error_for_package(&package);
3554
3555    if !is_indexing && latest_error.is_none() {
3556        return Empty.into_any();
3557    }
3558
3559    h_flex().gap_2().children(children).into_any_element()
3560}
3561
3562fn make_lsp_adapter_delegate(
3563    project: &Model<Project>,
3564    cx: &mut AppContext,
3565) -> Result<Arc<dyn LspAdapterDelegate>> {
3566    project.update(cx, |project, cx| {
3567        // TODO: Find the right worktree.
3568        let worktree = project
3569            .worktrees(cx)
3570            .next()
3571            .ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
3572        Ok(ProjectLspAdapterDelegate::new(project, &worktree, cx) as Arc<dyn LspAdapterDelegate>)
3573    })
3574}
3575
3576fn slash_command_error_block_renderer(message: String) -> RenderBlock {
3577    Box::new(move |_| {
3578        div()
3579            .pl_6()
3580            .child(
3581                Label::new(format!("error: {}", message))
3582                    .single_line()
3583                    .color(Color::Error),
3584            )
3585            .into_any()
3586    })
3587}
3588
3589enum TokenState {
3590    NoTokensLeft {
3591        max_token_count: usize,
3592        token_count: usize,
3593    },
3594    HasMoreTokens {
3595        max_token_count: usize,
3596        token_count: usize,
3597        over_warn_threshold: bool,
3598    },
3599}
3600
3601fn token_state(context: &Model<Context>, cx: &AppContext) -> Option<TokenState> {
3602    const WARNING_TOKEN_THRESHOLD: f32 = 0.8;
3603
3604    let model = LanguageModelRegistry::read_global(cx).active_model()?;
3605    let token_count = context.read(cx).token_count()?;
3606    let max_token_count = model.max_token_count();
3607
3608    let remaining_tokens = max_token_count as isize - token_count as isize;
3609    let token_state = if remaining_tokens <= 0 {
3610        TokenState::NoTokensLeft {
3611            max_token_count,
3612            token_count,
3613        }
3614    } else {
3615        let over_warn_threshold =
3616            token_count as f32 / max_token_count as f32 >= WARNING_TOKEN_THRESHOLD;
3617        TokenState::HasMoreTokens {
3618            max_token_count,
3619            token_count,
3620            over_warn_threshold,
3621        }
3622    };
3623    Some(token_state)
3624}
3625
3626enum ConfigurationError {
3627    NoProvider,
3628    ProviderNotAuthenticated,
3629}
3630
3631fn configuration_error(cx: &AppContext) -> Option<ConfigurationError> {
3632    let provider = LanguageModelRegistry::read_global(cx).active_provider();
3633    let is_authenticated = provider
3634        .as_ref()
3635        .map_or(false, |provider| provider.is_authenticated(cx));
3636
3637    if provider.is_some() && is_authenticated {
3638        return None;
3639    }
3640
3641    if provider.is_none() {
3642        return Some(ConfigurationError::NoProvider);
3643    }
3644
3645    if !is_authenticated {
3646        return Some(ConfigurationError::ProviderNotAuthenticated);
3647    }
3648
3649    None
3650}