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