assistant_panel.rs

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