assistant_panel.rs

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