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 selection = editor.update(cx, |editor, cx| editor.selections.newest_adjusted(cx));
1689        let editor = editor.read(cx);
1690        let buffer = editor.buffer().read(cx).snapshot(cx);
1691        let range = editor::ToOffset::to_offset(&selection.start, &buffer)
1692            ..editor::ToOffset::to_offset(&selection.end, &buffer);
1693        let start_language = buffer.language_at(range.start);
1694        let end_language = buffer.language_at(range.end);
1695        let language_name = if start_language == end_language {
1696            start_language.map(|language| language.code_fence_block_name())
1697        } else {
1698            None
1699        };
1700        let language_name = language_name.as_deref().unwrap_or("");
1701
1702        let selected_text = buffer.text_for_range(range).collect::<String>();
1703        let text = if selected_text.is_empty() {
1704            None
1705        } else {
1706            Some(if language_name == "markdown" {
1707                selected_text
1708                    .lines()
1709                    .map(|line| format!("> {}", line))
1710                    .collect::<Vec<_>>()
1711                    .join("\n")
1712            } else {
1713                format!("```{language_name}\n{selected_text}\n```")
1714            })
1715        };
1716
1717        // Activate the panel
1718        if !panel.focus_handle(cx).contains_focused(cx) {
1719            workspace.toggle_panel_focus::<AssistantPanel>(cx);
1720        }
1721
1722        if let Some(text) = text {
1723            panel.update(cx, |_, cx| {
1724                // Wait to create a new context until the workspace is no longer
1725                // being updated.
1726                cx.defer(move |panel, cx| {
1727                    if let Some(context) = panel
1728                        .active_context_editor(cx)
1729                        .or_else(|| panel.new_context(cx))
1730                    {
1731                        context.update(cx, |context, cx| {
1732                            context
1733                                .editor
1734                                .update(cx, |editor, cx| editor.insert(&text, cx))
1735                        });
1736                    };
1737                });
1738            });
1739        }
1740    }
1741
1742    fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
1743        let editor = self.editor.read(cx);
1744        let context = self.context.read(cx);
1745        if editor.selections.count() == 1 {
1746            let selection = editor.selections.newest::<usize>(cx);
1747            let mut copied_text = String::new();
1748            let mut spanned_messages = 0;
1749            for message in context.messages(cx) {
1750                if message.offset_range.start >= selection.range().end {
1751                    break;
1752                } else if message.offset_range.end >= selection.range().start {
1753                    let range = cmp::max(message.offset_range.start, selection.range().start)
1754                        ..cmp::min(message.offset_range.end, selection.range().end);
1755                    if !range.is_empty() {
1756                        spanned_messages += 1;
1757                        write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
1758                        for chunk in context.buffer().read(cx).text_for_range(range) {
1759                            copied_text.push_str(chunk);
1760                        }
1761                        copied_text.push('\n');
1762                    }
1763                }
1764            }
1765
1766            if spanned_messages > 1 {
1767                cx.write_to_clipboard(ClipboardItem::new(copied_text));
1768                return;
1769            }
1770        }
1771
1772        cx.propagate();
1773    }
1774
1775    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
1776        self.context.update(cx, |context, cx| {
1777            let selections = self.editor.read(cx).selections.disjoint_anchors();
1778            for selection in selections.as_ref() {
1779                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
1780                let range = selection
1781                    .map(|endpoint| endpoint.to_offset(&buffer))
1782                    .range();
1783                context.split_message(range, cx);
1784            }
1785        });
1786    }
1787
1788    fn apply_edit(&mut self, _: &ApplyEdit, cx: &mut ViewContext<Self>) {
1789        let Some(workspace) = self.workspace.upgrade() else {
1790            return;
1791        };
1792        let project = workspace.read(cx).project().clone();
1793
1794        struct Edit {
1795            old_text: String,
1796            new_text: String,
1797        }
1798
1799        let context = self.context.read(cx);
1800        let context_buffer = context.buffer().read(cx);
1801        let context_buffer_snapshot = context_buffer.snapshot();
1802
1803        let selections = self.editor.read(cx).selections.disjoint_anchors();
1804        let mut selections = selections.iter().peekable();
1805        let selected_suggestions = context
1806            .edit_suggestions()
1807            .iter()
1808            .filter(|suggestion| {
1809                while let Some(selection) = selections.peek() {
1810                    if selection
1811                        .end
1812                        .text_anchor
1813                        .cmp(&suggestion.source_range.start, context_buffer)
1814                        .is_lt()
1815                    {
1816                        selections.next();
1817                        continue;
1818                    }
1819                    if selection
1820                        .start
1821                        .text_anchor
1822                        .cmp(&suggestion.source_range.end, context_buffer)
1823                        .is_gt()
1824                    {
1825                        break;
1826                    }
1827                    return true;
1828                }
1829                false
1830            })
1831            .cloned()
1832            .collect::<Vec<_>>();
1833
1834        let mut opened_buffers: HashMap<PathBuf, Task<Result<Model<Buffer>>>> = HashMap::default();
1835        project.update(cx, |project, cx| {
1836            for suggestion in &selected_suggestions {
1837                opened_buffers
1838                    .entry(suggestion.full_path.clone())
1839                    .or_insert_with(|| {
1840                        project.open_buffer_for_full_path(&suggestion.full_path, cx)
1841                    });
1842            }
1843        });
1844
1845        cx.spawn(|this, mut cx| async move {
1846            let mut buffers_by_full_path = HashMap::default();
1847            for (full_path, buffer) in opened_buffers {
1848                if let Some(buffer) = buffer.await.log_err() {
1849                    buffers_by_full_path.insert(full_path, buffer);
1850                }
1851            }
1852
1853            let mut suggestions_by_buffer = HashMap::default();
1854            cx.update(|cx| {
1855                for suggestion in selected_suggestions {
1856                    if let Some(buffer) = buffers_by_full_path.get(&suggestion.full_path) {
1857                        let (_, edits) = suggestions_by_buffer
1858                            .entry(buffer.clone())
1859                            .or_insert_with(|| (buffer.read(cx).snapshot(), Vec::new()));
1860
1861                        let mut lines = context_buffer_snapshot
1862                            .as_rope()
1863                            .chunks_in_range(
1864                                suggestion.source_range.to_offset(&context_buffer_snapshot),
1865                            )
1866                            .lines();
1867                        if let Some(suggestion) = parse_next_edit_suggestion(&mut lines) {
1868                            let old_text = context_buffer_snapshot
1869                                .text_for_range(suggestion.old_text_range)
1870                                .collect();
1871                            let new_text = context_buffer_snapshot
1872                                .text_for_range(suggestion.new_text_range)
1873                                .collect();
1874                            edits.push(Edit { old_text, new_text });
1875                        }
1876                    }
1877                }
1878            })?;
1879
1880            let edits_by_buffer = cx
1881                .background_executor()
1882                .spawn(async move {
1883                    let mut result = HashMap::default();
1884                    for (buffer, (snapshot, suggestions)) in suggestions_by_buffer {
1885                        let edits =
1886                            result
1887                                .entry(buffer)
1888                                .or_insert(Vec::<(Range<language::Anchor>, _)>::new());
1889                        for suggestion in suggestions {
1890                            if let Some(range) =
1891                                fuzzy_search_lines(snapshot.as_rope(), &suggestion.old_text)
1892                            {
1893                                let edit_start = snapshot.anchor_after(range.start);
1894                                let edit_end = snapshot.anchor_before(range.end);
1895                                if let Err(ix) = edits.binary_search_by(|(range, _)| {
1896                                    range.start.cmp(&edit_start, &snapshot)
1897                                }) {
1898                                    edits.insert(
1899                                        ix,
1900                                        (edit_start..edit_end, suggestion.new_text.clone()),
1901                                    );
1902                                }
1903                            } else {
1904                                log::info!(
1905                                    "assistant edit did not match any text in buffer {:?}",
1906                                    &suggestion.old_text
1907                                );
1908                            }
1909                        }
1910                    }
1911                    result
1912                })
1913                .await;
1914
1915            let mut project_transaction = ProjectTransaction::default();
1916            let (editor, workspace, title) = this.update(&mut cx, |this, cx| {
1917                for (buffer_handle, edits) in edits_by_buffer {
1918                    buffer_handle.update(cx, |buffer, cx| {
1919                        buffer.start_transaction();
1920                        buffer.edit(
1921                            edits,
1922                            Some(AutoindentMode::Block {
1923                                original_indent_columns: Vec::new(),
1924                            }),
1925                            cx,
1926                        );
1927                        buffer.end_transaction(cx);
1928                        if let Some(transaction) = buffer.finalize_last_transaction() {
1929                            project_transaction
1930                                .0
1931                                .insert(buffer_handle.clone(), transaction.clone());
1932                        }
1933                    });
1934                }
1935
1936                (
1937                    this.editor.downgrade(),
1938                    this.workspace.clone(),
1939                    this.title(cx),
1940                )
1941            })?;
1942
1943            Editor::open_project_transaction(
1944                &editor,
1945                workspace,
1946                project_transaction,
1947                format!("Edits from {}", title),
1948                cx,
1949            )
1950            .await
1951        })
1952        .detach_and_log_err(cx);
1953    }
1954
1955    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
1956        self.context
1957            .update(cx, |context, cx| context.save(None, self.fs.clone(), cx));
1958    }
1959
1960    fn title(&self, cx: &AppContext) -> String {
1961        self.context
1962            .read(cx)
1963            .summary()
1964            .map(|summary| summary.text.clone())
1965            .unwrap_or_else(|| "New Context".into())
1966    }
1967
1968    fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1969        let focus_handle = self.focus_handle(cx).clone();
1970        ButtonLike::new("send_button")
1971            .style(ButtonStyle::Filled)
1972            .layer(ElevationIndex::ModalSurface)
1973            .children(
1974                KeyBinding::for_action_in(&Assist, &focus_handle, cx)
1975                    .map(|binding| binding.into_any_element()),
1976            )
1977            .child(Label::new("Send"))
1978            .on_click(move |_event, cx| {
1979                focus_handle.dispatch_action(&Assist, cx);
1980            })
1981    }
1982}
1983
1984impl EventEmitter<EditorEvent> for ContextEditor {}
1985impl EventEmitter<SearchEvent> for ContextEditor {}
1986
1987impl Render for ContextEditor {
1988    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1989        div()
1990            .key_context("ContextEditor")
1991            .capture_action(cx.listener(ContextEditor::cancel_last_assist))
1992            .capture_action(cx.listener(ContextEditor::save))
1993            .capture_action(cx.listener(ContextEditor::copy))
1994            .capture_action(cx.listener(ContextEditor::cycle_message_role))
1995            .capture_action(cx.listener(ContextEditor::confirm_command))
1996            .on_action(cx.listener(ContextEditor::assist))
1997            .on_action(cx.listener(ContextEditor::split))
1998            .on_action(cx.listener(ContextEditor::apply_edit))
1999            .size_full()
2000            .v_flex()
2001            .child(
2002                div()
2003                    .flex_grow()
2004                    .bg(cx.theme().colors().editor_background)
2005                    .child(self.editor.clone())
2006                    .child(
2007                        h_flex()
2008                            .w_full()
2009                            .absolute()
2010                            .bottom_0()
2011                            .p_4()
2012                            .justify_end()
2013                            .child(self.render_send_button(cx)),
2014                    ),
2015            )
2016    }
2017}
2018
2019impl FocusableView for ContextEditor {
2020    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
2021        self.editor.focus_handle(cx)
2022    }
2023}
2024
2025impl Item for ContextEditor {
2026    type Event = editor::EditorEvent;
2027
2028    fn tab_content_text(&self, cx: &WindowContext) -> Option<SharedString> {
2029        Some(util::truncate_and_trailoff(&self.title(cx), Self::MAX_TAB_TITLE_LEN).into())
2030    }
2031
2032    fn to_item_events(event: &Self::Event, mut f: impl FnMut(item::ItemEvent)) {
2033        match event {
2034            EditorEvent::Edited { .. } => {
2035                f(item::ItemEvent::Edit);
2036                f(item::ItemEvent::UpdateBreadcrumbs);
2037            }
2038            EditorEvent::TitleChanged => {
2039                f(item::ItemEvent::UpdateTab);
2040            }
2041            _ => {}
2042        }
2043    }
2044
2045    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
2046        Some(self.title(cx).into())
2047    }
2048
2049    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
2050        Some(Box::new(handle.clone()))
2051    }
2052
2053    fn breadcrumbs(
2054        &self,
2055        theme: &theme::Theme,
2056        cx: &AppContext,
2057    ) -> Option<Vec<item::BreadcrumbText>> {
2058        let editor = self.editor.read(cx);
2059        let cursor = editor.selections.newest_anchor().head();
2060        let multibuffer = &editor.buffer().read(cx);
2061        let (_, symbols) = multibuffer.symbols_containing(cursor, Some(&theme.syntax()), cx)?;
2062
2063        let settings = ThemeSettings::get_global(cx);
2064
2065        let mut breadcrumbs = Vec::new();
2066
2067        let title = self.title(cx);
2068        if title.chars().count() > Self::MAX_TAB_TITLE_LEN {
2069            breadcrumbs.push(BreadcrumbText {
2070                text: title,
2071                highlights: None,
2072                font: Some(settings.buffer_font.clone()),
2073            });
2074        }
2075
2076        breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
2077            text: symbol.text,
2078            highlights: Some(symbol.highlight_ranges),
2079            font: Some(settings.buffer_font.clone()),
2080        }));
2081        Some(breadcrumbs)
2082    }
2083
2084    fn breadcrumb_location(&self) -> ToolbarItemLocation {
2085        ToolbarItemLocation::PrimaryLeft
2086    }
2087
2088    fn set_nav_history(&mut self, nav_history: pane::ItemNavHistory, cx: &mut ViewContext<Self>) {
2089        self.editor.update(cx, |editor, cx| {
2090            Item::set_nav_history(editor, nav_history, cx)
2091        })
2092    }
2093
2094    fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
2095        self.editor
2096            .update(cx, |editor, cx| Item::navigate(editor, data, cx))
2097    }
2098
2099    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
2100        self.editor
2101            .update(cx, |editor, cx| Item::deactivated(editor, cx))
2102    }
2103}
2104
2105impl SearchableItem for ContextEditor {
2106    type Match = <Editor as SearchableItem>::Match;
2107
2108    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
2109        self.editor.update(cx, |editor, cx| {
2110            editor.clear_matches(cx);
2111        });
2112    }
2113
2114    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
2115        self.editor
2116            .update(cx, |editor, cx| editor.update_matches(matches, cx));
2117    }
2118
2119    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
2120        self.editor
2121            .update(cx, |editor, cx| editor.query_suggestion(cx))
2122    }
2123
2124    fn activate_match(
2125        &mut self,
2126        index: usize,
2127        matches: &[Self::Match],
2128        cx: &mut ViewContext<Self>,
2129    ) {
2130        self.editor.update(cx, |editor, cx| {
2131            editor.activate_match(index, matches, cx);
2132        });
2133    }
2134
2135    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
2136        self.editor
2137            .update(cx, |editor, cx| editor.select_matches(matches, cx));
2138    }
2139
2140    fn replace(
2141        &mut self,
2142        identifier: &Self::Match,
2143        query: &project::search::SearchQuery,
2144        cx: &mut ViewContext<Self>,
2145    ) {
2146        self.editor
2147            .update(cx, |editor, cx| editor.replace(identifier, query, cx));
2148    }
2149
2150    fn find_matches(
2151        &mut self,
2152        query: Arc<project::search::SearchQuery>,
2153        cx: &mut ViewContext<Self>,
2154    ) -> Task<Vec<Self::Match>> {
2155        self.editor
2156            .update(cx, |editor, cx| editor.find_matches(query, cx))
2157    }
2158
2159    fn active_match_index(
2160        &mut self,
2161        matches: &[Self::Match],
2162        cx: &mut ViewContext<Self>,
2163    ) -> Option<usize> {
2164        self.editor
2165            .update(cx, |editor, cx| editor.active_match_index(matches, cx))
2166    }
2167}
2168
2169impl FollowableItem for ContextEditor {
2170    fn remote_id(&self) -> Option<workspace::ViewId> {
2171        self.remote_id
2172    }
2173
2174    fn to_state_proto(&self, cx: &WindowContext) -> Option<proto::view::Variant> {
2175        let context = self.context.read(cx);
2176        Some(proto::view::Variant::ContextEditor(
2177            proto::view::ContextEditor {
2178                context_id: context.id().to_proto(),
2179                editor: if let Some(proto::view::Variant::Editor(proto)) =
2180                    self.editor.read(cx).to_state_proto(cx)
2181                {
2182                    Some(proto)
2183                } else {
2184                    None
2185                },
2186            },
2187        ))
2188    }
2189
2190    fn from_state_proto(
2191        workspace: View<Workspace>,
2192        id: workspace::ViewId,
2193        state: &mut Option<proto::view::Variant>,
2194        cx: &mut WindowContext,
2195    ) -> Option<Task<Result<View<Self>>>> {
2196        let proto::view::Variant::ContextEditor(_) = state.as_ref()? else {
2197            return None;
2198        };
2199        let Some(proto::view::Variant::ContextEditor(state)) = state.take() else {
2200            unreachable!()
2201        };
2202
2203        let context_id = ContextId::from_proto(state.context_id);
2204        let editor_state = state.editor?;
2205
2206        let (project, panel) = workspace.update(cx, |workspace, cx| {
2207            Some((
2208                workspace.project().clone(),
2209                workspace.panel::<AssistantPanel>(cx)?,
2210            ))
2211        })?;
2212
2213        let context_editor =
2214            panel.update(cx, |panel, cx| panel.open_remote_context(context_id, cx));
2215
2216        Some(cx.spawn(|mut cx| async move {
2217            let context_editor = context_editor.await?;
2218            context_editor
2219                .update(&mut cx, |context_editor, cx| {
2220                    context_editor.remote_id = Some(id);
2221                    context_editor.editor.update(cx, |editor, cx| {
2222                        editor.apply_update_proto(
2223                            &project,
2224                            proto::update_view::Variant::Editor(proto::update_view::Editor {
2225                                selections: editor_state.selections,
2226                                pending_selection: editor_state.pending_selection,
2227                                scroll_top_anchor: editor_state.scroll_top_anchor,
2228                                scroll_x: editor_state.scroll_y,
2229                                scroll_y: editor_state.scroll_y,
2230                                ..Default::default()
2231                            }),
2232                            cx,
2233                        )
2234                    })
2235                })?
2236                .await?;
2237            Ok(context_editor)
2238        }))
2239    }
2240
2241    fn to_follow_event(event: &Self::Event) -> Option<item::FollowEvent> {
2242        Editor::to_follow_event(event)
2243    }
2244
2245    fn add_event_to_update_proto(
2246        &self,
2247        event: &Self::Event,
2248        update: &mut Option<proto::update_view::Variant>,
2249        cx: &WindowContext,
2250    ) -> bool {
2251        self.editor
2252            .read(cx)
2253            .add_event_to_update_proto(event, update, cx)
2254    }
2255
2256    fn apply_update_proto(
2257        &mut self,
2258        project: &Model<Project>,
2259        message: proto::update_view::Variant,
2260        cx: &mut ViewContext<Self>,
2261    ) -> Task<Result<()>> {
2262        self.editor.update(cx, |editor, cx| {
2263            editor.apply_update_proto(project, message, cx)
2264        })
2265    }
2266
2267    fn is_project_item(&self, _cx: &WindowContext) -> bool {
2268        true
2269    }
2270
2271    fn set_leader_peer_id(
2272        &mut self,
2273        leader_peer_id: Option<proto::PeerId>,
2274        cx: &mut ViewContext<Self>,
2275    ) {
2276        self.editor.update(cx, |editor, cx| {
2277            editor.set_leader_peer_id(leader_peer_id, cx)
2278        })
2279    }
2280
2281    fn dedup(&self, existing: &Self, cx: &WindowContext) -> Option<item::Dedup> {
2282        if existing.context.read(cx).id() == self.context.read(cx).id() {
2283            Some(item::Dedup::KeepExisting)
2284        } else {
2285            None
2286        }
2287    }
2288}
2289
2290pub struct ContextEditorToolbarItem {
2291    fs: Arc<dyn Fs>,
2292    workspace: WeakView<Workspace>,
2293    active_context_editor: Option<WeakView<ContextEditor>>,
2294    model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
2295}
2296
2297impl ContextEditorToolbarItem {
2298    pub fn new(
2299        workspace: &Workspace,
2300        model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
2301    ) -> Self {
2302        Self {
2303            fs: workspace.app_state().fs.clone(),
2304            workspace: workspace.weak_handle(),
2305            active_context_editor: None,
2306            model_selector_menu_handle,
2307        }
2308    }
2309
2310    fn render_inject_context_menu(&self, cx: &mut ViewContext<Self>) -> impl Element {
2311        let commands = SlashCommandRegistry::global(cx);
2312        let active_editor_focus_handle = self.workspace.upgrade().and_then(|workspace| {
2313            Some(
2314                workspace
2315                    .read(cx)
2316                    .active_item_as::<Editor>(cx)?
2317                    .focus_handle(cx),
2318            )
2319        });
2320        let active_context_editor = self.active_context_editor.clone();
2321
2322        PopoverMenu::new("inject-context-menu")
2323            .trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
2324                Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
2325            }))
2326            .menu(move |cx| {
2327                let active_context_editor = active_context_editor.clone()?;
2328                ContextMenu::build(cx, |mut menu, _cx| {
2329                    for command_name in commands.featured_command_names() {
2330                        if let Some(command) = commands.command(&command_name) {
2331                            let menu_text = SharedString::from(Arc::from(command.menu_text()));
2332                            menu = menu.custom_entry(
2333                                {
2334                                    let command_name = command_name.clone();
2335                                    move |_cx| {
2336                                        h_flex()
2337                                            .gap_4()
2338                                            .w_full()
2339                                            .justify_between()
2340                                            .child(Label::new(menu_text.clone()))
2341                                            .child(
2342                                                Label::new(format!("/{command_name}"))
2343                                                    .color(Color::Muted),
2344                                            )
2345                                            .into_any()
2346                                    }
2347                                },
2348                                {
2349                                    let active_context_editor = active_context_editor.clone();
2350                                    move |cx| {
2351                                        active_context_editor
2352                                            .update(cx, |context_editor, cx| {
2353                                                context_editor.insert_command(&command_name, cx)
2354                                            })
2355                                            .ok();
2356                                    }
2357                                },
2358                            )
2359                        }
2360                    }
2361
2362                    if let Some(active_editor_focus_handle) = active_editor_focus_handle.clone() {
2363                        menu = menu
2364                            .context(active_editor_focus_handle)
2365                            .action("Quote Selection", Box::new(QuoteSelection));
2366                    }
2367
2368                    menu
2369                })
2370                .into()
2371            })
2372    }
2373
2374    fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
2375        let model = CompletionProvider::global(cx).model();
2376        let context = &self
2377            .active_context_editor
2378            .as_ref()?
2379            .upgrade()?
2380            .read(cx)
2381            .context;
2382        let token_count = context.read(cx).token_count()?;
2383        let max_token_count = model.max_token_count();
2384
2385        let remaining_tokens = max_token_count as isize - token_count as isize;
2386        let token_count_color = if remaining_tokens <= 0 {
2387            Color::Error
2388        } else if token_count as f32 / max_token_count as f32 >= 0.8 {
2389            Color::Warning
2390        } else {
2391            Color::Muted
2392        };
2393
2394        Some(
2395            h_flex()
2396                .gap_0p5()
2397                .child(
2398                    Label::new(humanize_token_count(token_count))
2399                        .size(LabelSize::Small)
2400                        .color(token_count_color),
2401                )
2402                .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
2403                .child(
2404                    Label::new(humanize_token_count(max_token_count))
2405                        .size(LabelSize::Small)
2406                        .color(Color::Muted),
2407                ),
2408        )
2409    }
2410}
2411
2412impl Render for ContextEditorToolbarItem {
2413    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
2414        h_flex()
2415            .gap_2()
2416            .child(ModelSelector::new(
2417                self.model_selector_menu_handle.clone(),
2418                self.fs.clone(),
2419            ))
2420            .children(self.render_remaining_tokens(cx))
2421            .child(self.render_inject_context_menu(cx))
2422    }
2423}
2424
2425impl ToolbarItemView for ContextEditorToolbarItem {
2426    fn set_active_pane_item(
2427        &mut self,
2428        active_pane_item: Option<&dyn ItemHandle>,
2429        cx: &mut ViewContext<Self>,
2430    ) -> ToolbarItemLocation {
2431        self.active_context_editor = active_pane_item
2432            .and_then(|item| item.act_as::<ContextEditor>(cx))
2433            .map(|editor| editor.downgrade());
2434        cx.notify();
2435        if self.active_context_editor.is_none() {
2436            ToolbarItemLocation::Hidden
2437        } else {
2438            ToolbarItemLocation::PrimaryRight
2439        }
2440    }
2441
2442    fn pane_focus_update(&mut self, _pane_focused: bool, cx: &mut ViewContext<Self>) {
2443        cx.notify();
2444    }
2445}
2446
2447impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
2448
2449pub struct ContextHistory {
2450    picker: View<Picker<SavedContextPickerDelegate>>,
2451    _subscriptions: Vec<Subscription>,
2452    assistant_panel: WeakView<AssistantPanel>,
2453}
2454
2455impl ContextHistory {
2456    fn new(
2457        project: Model<Project>,
2458        context_store: Model<ContextStore>,
2459        assistant_panel: WeakView<AssistantPanel>,
2460        cx: &mut ViewContext<Self>,
2461    ) -> Self {
2462        let picker = cx.new_view(|cx| {
2463            Picker::uniform_list(
2464                SavedContextPickerDelegate::new(project, context_store.clone()),
2465                cx,
2466            )
2467            .modal(false)
2468            .max_height(None)
2469        });
2470
2471        let _subscriptions = vec![
2472            cx.observe(&context_store, |this, _, cx| {
2473                this.picker.update(cx, |picker, cx| picker.refresh(cx));
2474            }),
2475            cx.subscribe(&picker, Self::handle_picker_event),
2476        ];
2477
2478        Self {
2479            picker,
2480            _subscriptions,
2481            assistant_panel,
2482        }
2483    }
2484
2485    fn handle_picker_event(
2486        &mut self,
2487        _: View<Picker<SavedContextPickerDelegate>>,
2488        event: &SavedContextPickerEvent,
2489        cx: &mut ViewContext<Self>,
2490    ) {
2491        let SavedContextPickerEvent::Confirmed(context) = event;
2492        self.assistant_panel
2493            .update(cx, |assistant_panel, cx| match context {
2494                ContextMetadata::Remote(metadata) => {
2495                    assistant_panel
2496                        .open_remote_context(metadata.id.clone(), cx)
2497                        .detach_and_log_err(cx);
2498                }
2499                ContextMetadata::Saved(metadata) => {
2500                    assistant_panel
2501                        .open_saved_context(metadata.path.clone(), cx)
2502                        .detach_and_log_err(cx);
2503                }
2504            })
2505            .ok();
2506    }
2507}
2508
2509impl Render for ContextHistory {
2510    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
2511        div().size_full().child(self.picker.clone())
2512    }
2513}
2514
2515impl FocusableView for ContextHistory {
2516    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
2517        self.picker.focus_handle(cx)
2518    }
2519}
2520
2521impl EventEmitter<()> for ContextHistory {}
2522
2523impl Item for ContextHistory {
2524    type Event = ();
2525
2526    fn tab_content_text(&self, _cx: &WindowContext) -> Option<SharedString> {
2527        Some("History".into())
2528    }
2529}
2530
2531type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
2532
2533fn render_slash_command_output_toggle(
2534    row: MultiBufferRow,
2535    is_folded: bool,
2536    fold: ToggleFold,
2537    _cx: &mut WindowContext,
2538) -> AnyElement {
2539    Disclosure::new(
2540        ("slash-command-output-fold-indicator", row.0 as u64),
2541        !is_folded,
2542    )
2543    .selected(is_folded)
2544    .on_click(move |_e, cx| fold(!is_folded, cx))
2545    .into_any_element()
2546}
2547
2548fn render_pending_slash_command_gutter_decoration(
2549    row: MultiBufferRow,
2550    status: &PendingSlashCommandStatus,
2551    confirm_command: Arc<dyn Fn(&mut WindowContext)>,
2552) -> AnyElement {
2553    let mut icon = IconButton::new(
2554        ("slash-command-gutter-decoration", row.0),
2555        ui::IconName::TriangleRight,
2556    )
2557    .on_click(move |_e, cx| confirm_command(cx))
2558    .icon_size(ui::IconSize::Small)
2559    .size(ui::ButtonSize::None);
2560
2561    match status {
2562        PendingSlashCommandStatus::Idle => {
2563            icon = icon.icon_color(Color::Muted);
2564        }
2565        PendingSlashCommandStatus::Running { .. } => {
2566            icon = icon.selected(true);
2567        }
2568        PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
2569    }
2570
2571    icon.into_any_element()
2572}
2573
2574fn render_docs_slash_command_trailer(
2575    row: MultiBufferRow,
2576    command: PendingSlashCommand,
2577    cx: &mut WindowContext,
2578) -> AnyElement {
2579    let Some(argument) = command.argument else {
2580        return Empty.into_any();
2581    };
2582
2583    let args = DocsSlashCommandArgs::parse(&argument);
2584
2585    let Some(store) = args
2586        .provider()
2587        .and_then(|provider| IndexedDocsStore::try_global(provider, cx).ok())
2588    else {
2589        return Empty.into_any();
2590    };
2591
2592    let Some(package) = args.package() else {
2593        return Empty.into_any();
2594    };
2595
2596    let mut children = Vec::new();
2597
2598    if store.is_indexing(&package) {
2599        children.push(
2600            div()
2601                .id(("crates-being-indexed", row.0))
2602                .child(Icon::new(IconName::ArrowCircle).with_animation(
2603                    "arrow-circle",
2604                    Animation::new(Duration::from_secs(4)).repeat(),
2605                    |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
2606                ))
2607                .tooltip({
2608                    let package = package.clone();
2609                    move |cx| Tooltip::text(format!("Indexing {package}"), cx)
2610                })
2611                .into_any_element(),
2612        );
2613    }
2614
2615    if let Some(latest_error) = store.latest_error_for_package(&package) {
2616        children.push(
2617            div()
2618                .id(("latest-error", row.0))
2619                .child(Icon::new(IconName::ExclamationTriangle).color(Color::Warning))
2620                .tooltip(move |cx| Tooltip::text(format!("failed to index: {latest_error}"), cx))
2621                .into_any_element(),
2622        )
2623    }
2624
2625    let is_indexing = store.is_indexing(&package);
2626    let latest_error = store.latest_error_for_package(&package);
2627
2628    if !is_indexing && latest_error.is_none() {
2629        return Empty.into_any();
2630    }
2631
2632    h_flex().gap_2().children(children).into_any_element()
2633}
2634
2635fn make_lsp_adapter_delegate(
2636    project: &Model<Project>,
2637    cx: &mut AppContext,
2638) -> Result<Arc<dyn LspAdapterDelegate>> {
2639    project.update(cx, |project, cx| {
2640        // TODO: Find the right worktree.
2641        let worktree = project
2642            .worktrees()
2643            .next()
2644            .ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
2645        Ok(ProjectLspAdapterDelegate::new(project, &worktree, cx) as Arc<dyn LspAdapterDelegate>)
2646    })
2647}
2648
2649fn slash_command_error_block_renderer(message: String) -> RenderBlock {
2650    Box::new(move |_| {
2651        div()
2652            .pl_6()
2653            .child(
2654                Label::new(format!("error: {}", message))
2655                    .single_line()
2656                    .color(Color::Error),
2657            )
2658            .into_any()
2659    })
2660}