assistant_panel.rs

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