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