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