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