assistant_panel.rs

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