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