assistant_panel.rs

   1use crate::slash_command::docs_command::{DocsSlashCommand, DocsSlashCommandArgs};
   2use crate::{
   3    assistant_settings::{AssistantDockPosition, AssistantSettings},
   4    humanize_token_count,
   5    prompt_library::open_prompt_library,
   6    search::*,
   7    slash_command::{
   8        default_command::DefaultSlashCommand, SlashCommandCompletionProvider, SlashCommandLine,
   9        SlashCommandRegistry,
  10    },
  11    terminal_inline_assistant::TerminalInlineAssistant,
  12    ApplyEdit, Assist, CompletionProvider, ConfirmCommand, ContextStore, CycleMessageRole,
  13    DeployHistory, DeployPromptLibrary, InlineAssist, InlineAssistant, InsertIntoEditor,
  14    LanguageModelRequest, LanguageModelRequestMessage, MessageId, MessageMetadata, MessageStatus,
  15    ModelSelector, QuoteSelection, ResetKey, Role, SavedContext, SavedContextMetadata,
  16    SavedMessage, Split, ToggleFocus, ToggleModelSelector,
  17};
  18use anyhow::{anyhow, Result};
  19use assistant_slash_command::{SlashCommand, SlashCommandOutput, SlashCommandOutputSection};
  20use breadcrumbs::Breadcrumbs;
  21use client::telemetry::Telemetry;
  22use collections::{BTreeSet, HashMap, HashSet};
  23use editor::{
  24    actions::{FoldAt, MoveToEndOfLine, Newline, ShowCompletions, UnfoldAt},
  25    display_map::{
  26        BlockDisposition, BlockId, BlockProperties, BlockStyle, Crease, RenderBlock, ToDisplayPoint,
  27    },
  28    scroll::{Autoscroll, AutoscrollStrategy},
  29    Anchor, Editor, EditorEvent, RowExt, ToOffset as _, ToPoint,
  30};
  31use editor::{display_map::CreaseId, FoldPlaceholder};
  32use fs::Fs;
  33use futures::future::Shared;
  34use futures::{FutureExt, StreamExt};
  35use gpui::{
  36    div, percentage, point, Action, Animation, AnimationExt, AnyElement, AnyView, AppContext,
  37    AsyncAppContext, AsyncWindowContext, ClipboardItem, Context as _, DismissEvent, Empty,
  38    EventEmitter, FocusHandle, FocusableView, InteractiveElement, IntoElement, Model, ModelContext,
  39    ParentElement, Pixels, Render, SharedString, StatefulInteractiveElement, Styled, Subscription,
  40    Task, Transformation, UpdateGlobal, View, ViewContext, VisualContext, WeakView, WindowContext,
  41};
  42use indexed_docs::IndexedDocsStore;
  43use language::{
  44    language_settings::SoftWrap, AnchorRangeExt as _, AutoindentMode, Buffer, LanguageRegistry,
  45    LspAdapterDelegate, OffsetRangeExt as _, Point, ToOffset as _,
  46};
  47use multi_buffer::MultiBufferRow;
  48use paths::contexts_dir;
  49use picker::{Picker, PickerDelegate};
  50use project::{Project, ProjectLspAdapterDelegate, ProjectTransaction};
  51use search::{buffer_search::DivRegistrar, BufferSearchBar};
  52use settings::Settings;
  53use std::{
  54    cmp::{self, Ordering},
  55    fmt::Write,
  56    iter,
  57    ops::Range,
  58    path::PathBuf,
  59    sync::Arc,
  60    time::{Duration, Instant},
  61};
  62use telemetry_events::AssistantKind;
  63use terminal_view::{terminal_panel::TerminalPanel, TerminalView};
  64use theme::ThemeSettings;
  65use ui::{
  66    prelude::*, ButtonLike, ContextMenu, Disclosure, ElevationIndex, KeyBinding, ListItem,
  67    ListItemSpacing, PopoverMenu, PopoverMenuHandle, Tooltip,
  68};
  69use util::{post_inc, ResultExt, TryFutureExt};
  70use uuid::Uuid;
  71use workspace::{
  72    dock::{DockPosition, Panel, PanelEvent},
  73    item::{BreadcrumbText, Item, ItemHandle},
  74    pane,
  75    searchable::{SearchEvent, SearchableItem},
  76    Pane, Save, ToggleZoom, ToolbarItemEvent, ToolbarItemLocation, ToolbarItemView, Workspace,
  77};
  78use workspace::{searchable::SearchableItemHandle, NewFile};
  79
  80pub fn init(cx: &mut AppContext) {
  81    cx.observe_new_views(
  82        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
  83            workspace
  84                .register_action(|workspace, _: &ToggleFocus, cx| {
  85                    let settings = AssistantSettings::get_global(cx);
  86                    if !settings.enabled {
  87                        return;
  88                    }
  89
  90                    workspace.toggle_panel_focus::<AssistantPanel>(cx);
  91                })
  92                .register_action(AssistantPanel::inline_assist)
  93                .register_action(ContextEditor::quote_selection)
  94                .register_action(ContextEditor::insert_selection);
  95        },
  96    )
  97    .detach();
  98}
  99
 100pub enum AssistantPanelEvent {
 101    ContextEdited,
 102}
 103
 104pub struct AssistantPanel {
 105    pane: View<Pane>,
 106    workspace: WeakView<Workspace>,
 107    width: Option<Pixels>,
 108    height: Option<Pixels>,
 109    context_store: Model<ContextStore>,
 110    languages: Arc<LanguageRegistry>,
 111    slash_commands: Arc<SlashCommandRegistry>,
 112    fs: Arc<dyn Fs>,
 113    telemetry: Arc<Telemetry>,
 114    subscriptions: Vec<Subscription>,
 115    authentication_prompt: Option<AnyView>,
 116    model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
 117}
 118
 119struct SavedContextPickerDelegate {
 120    store: Model<ContextStore>,
 121    matches: Vec<SavedContextMetadata>,
 122    selected_index: usize,
 123}
 124
 125enum SavedContextPickerEvent {
 126    Confirmed { path: PathBuf },
 127}
 128
 129enum InlineAssistTarget {
 130    Editor(View<Editor>, bool),
 131    Terminal(View<TerminalView>),
 132}
 133
 134impl EventEmitter<SavedContextPickerEvent> for Picker<SavedContextPickerDelegate> {}
 135
 136impl SavedContextPickerDelegate {
 137    fn new(store: Model<ContextStore>) -> Self {
 138        Self {
 139            store,
 140            matches: Vec::new(),
 141            selected_index: 0,
 142        }
 143    }
 144}
 145
 146impl PickerDelegate for SavedContextPickerDelegate {
 147    type ListItem = ListItem;
 148
 149    fn match_count(&self) -> usize {
 150        self.matches.len()
 151    }
 152
 153    fn selected_index(&self) -> usize {
 154        self.selected_index
 155    }
 156
 157    fn set_selected_index(&mut self, ix: usize, _cx: &mut ViewContext<Picker<Self>>) {
 158        self.selected_index = ix;
 159    }
 160
 161    fn placeholder_text(&self, _cx: &mut WindowContext) -> Arc<str> {
 162        "Search...".into()
 163    }
 164
 165    fn update_matches(&mut self, query: String, cx: &mut ViewContext<Picker<Self>>) -> Task<()> {
 166        let search = self.store.read(cx).search(query, cx);
 167        cx.spawn(|this, mut cx| async move {
 168            let matches = search.await;
 169            this.update(&mut cx, |this, cx| {
 170                this.delegate.matches = matches;
 171                this.delegate.selected_index = 0;
 172                cx.notify();
 173            })
 174            .ok();
 175        })
 176    }
 177
 178    fn confirm(&mut self, _secondary: bool, cx: &mut ViewContext<Picker<Self>>) {
 179        if let Some(metadata) = self.matches.get(self.selected_index) {
 180            cx.emit(SavedContextPickerEvent::Confirmed {
 181                path: metadata.path.clone(),
 182            })
 183        }
 184    }
 185
 186    fn dismissed(&mut self, _cx: &mut ViewContext<Picker<Self>>) {}
 187
 188    fn render_match(
 189        &self,
 190        ix: usize,
 191        selected: bool,
 192        _cx: &mut ViewContext<Picker<Self>>,
 193    ) -> Option<Self::ListItem> {
 194        let context = self.matches.get(ix)?;
 195        Some(
 196            ListItem::new(ix)
 197                .inset(true)
 198                .spacing(ListItemSpacing::Sparse)
 199                .selected(selected)
 200                .child(
 201                    div()
 202                        .flex()
 203                        .w_full()
 204                        .gap_2()
 205                        .child(
 206                            Label::new(context.mtime.format("%F %I:%M%p").to_string())
 207                                .color(Color::Muted)
 208                                .size(LabelSize::Small),
 209                        )
 210                        .child(Label::new(context.title.clone()).size(LabelSize::Small)),
 211                ),
 212        )
 213    }
 214}
 215
 216impl AssistantPanel {
 217    pub fn load(
 218        workspace: WeakView<Workspace>,
 219        cx: AsyncWindowContext,
 220    ) -> Task<Result<View<Self>>> {
 221        cx.spawn(|mut cx| async move {
 222            // TODO: deserialize state.
 223            let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
 224            let context_store = cx.update(|cx| ContextStore::new(fs.clone(), cx))?.await?;
 225            workspace.update(&mut cx, |workspace, cx| {
 226                cx.new_view(|cx| Self::new(workspace, context_store.clone(), cx))
 227            })
 228        })
 229    }
 230
 231    fn new(
 232        workspace: &Workspace,
 233        context_store: Model<ContextStore>,
 234        cx: &mut ViewContext<Self>,
 235    ) -> Self {
 236        let model_selector_menu_handle = PopoverMenuHandle::default();
 237        let pane = cx.new_view(|cx| {
 238            let mut pane = Pane::new(
 239                workspace.weak_handle(),
 240                workspace.project().clone(),
 241                Default::default(),
 242                None,
 243                NewFile.boxed_clone(),
 244                cx,
 245            );
 246            pane.set_can_split(false, cx);
 247            pane.set_can_navigate(true, cx);
 248            pane.display_nav_history_buttons(None);
 249            pane.set_should_display_tab_bar(|_| true);
 250            pane.set_render_tab_bar_buttons(cx, move |pane, cx| {
 251                h_flex()
 252                    .gap(Spacing::Small.rems(cx))
 253                    .child(
 254                        IconButton::new("menu", IconName::Menu)
 255                            .icon_size(IconSize::Small)
 256                            .on_click(cx.listener(|pane, _, cx| {
 257                                let zoom_label = if pane.is_zoomed() {
 258                                    "Zoom Out"
 259                                } else {
 260                                    "Zoom In"
 261                                };
 262                                let menu = ContextMenu::build(cx, |menu, cx| {
 263                                    menu.context(pane.focus_handle(cx))
 264                                        .action("New Context", Box::new(NewFile))
 265                                        .action("History", Box::new(DeployHistory))
 266                                        .action("Prompt Library", Box::new(DeployPromptLibrary))
 267                                        .action(zoom_label, Box::new(ToggleZoom))
 268                                });
 269                                cx.subscribe(&menu, |pane, _, _: &DismissEvent, _| {
 270                                    pane.new_item_menu = None;
 271                                })
 272                                .detach();
 273                                pane.new_item_menu = Some(menu);
 274                            })),
 275                    )
 276                    .when_some(pane.new_item_menu.as_ref(), |el, new_item_menu| {
 277                        el.child(Pane::render_menu_overlay(new_item_menu))
 278                    })
 279                    .into_any_element()
 280            });
 281            pane.toolbar().update(cx, |toolbar, cx| {
 282                toolbar.add_item(cx.new_view(|_| Breadcrumbs::new()), cx);
 283                toolbar.add_item(
 284                    cx.new_view(|_| {
 285                        ContextEditorToolbarItem::new(workspace, model_selector_menu_handle.clone())
 286                    }),
 287                    cx,
 288                );
 289                toolbar.add_item(cx.new_view(BufferSearchBar::new), cx)
 290            });
 291            pane
 292        });
 293
 294        let subscriptions = vec![
 295            cx.observe(&pane, |_, _, cx| cx.notify()),
 296            cx.subscribe(&pane, Self::handle_pane_event),
 297            cx.observe_global::<CompletionProvider>({
 298                let mut prev_settings_version = CompletionProvider::global(cx).settings_version();
 299                move |this, cx| {
 300                    this.completion_provider_changed(prev_settings_version, cx);
 301                    prev_settings_version = CompletionProvider::global(cx).settings_version();
 302                }
 303            }),
 304        ];
 305
 306        Self {
 307            pane,
 308            workspace: workspace.weak_handle(),
 309            width: None,
 310            height: None,
 311            context_store,
 312            languages: workspace.app_state().languages.clone(),
 313            slash_commands: SlashCommandRegistry::global(cx),
 314            fs: workspace.app_state().fs.clone(),
 315            telemetry: workspace.client().telemetry().clone(),
 316            subscriptions,
 317            authentication_prompt: None,
 318            model_selector_menu_handle,
 319        }
 320    }
 321
 322    fn handle_pane_event(
 323        &mut self,
 324        _pane: View<Pane>,
 325        event: &pane::Event,
 326        cx: &mut ViewContext<Self>,
 327    ) {
 328        match event {
 329            pane::Event::Remove => cx.emit(PanelEvent::Close),
 330            pane::Event::ZoomIn => cx.emit(PanelEvent::ZoomIn),
 331            pane::Event::ZoomOut => cx.emit(PanelEvent::ZoomOut),
 332
 333            pane::Event::AddItem { item } => {
 334                if let Some(workspace) = self.workspace.upgrade() {
 335                    workspace.update(cx, |workspace, cx| {
 336                        item.added_to_pane(workspace, self.pane.clone(), cx)
 337                    });
 338                }
 339            }
 340
 341            pane::Event::RemoveItem { .. } | pane::Event::ActivateItem { .. } => {
 342                cx.emit(AssistantPanelEvent::ContextEdited);
 343            }
 344
 345            _ => {}
 346        }
 347    }
 348
 349    fn completion_provider_changed(
 350        &mut self,
 351        prev_settings_version: usize,
 352        cx: &mut ViewContext<Self>,
 353    ) {
 354        if self.is_authenticated(cx) {
 355            self.authentication_prompt = None;
 356
 357            if let Some(editor) = self.active_context_editor(cx) {
 358                editor.update(cx, |active_context, cx| {
 359                    active_context
 360                        .context
 361                        .update(cx, |context, cx| context.completion_provider_changed(cx))
 362                })
 363            }
 364
 365            if self.active_context_editor(cx).is_none() {
 366                self.new_context(cx);
 367            }
 368            cx.notify();
 369        } else if self.authentication_prompt.is_none()
 370            || prev_settings_version != CompletionProvider::global(cx).settings_version()
 371        {
 372            self.authentication_prompt =
 373                Some(cx.update_global::<CompletionProvider, _>(|provider, cx| {
 374                    provider.authentication_prompt(cx)
 375                }));
 376            cx.notify();
 377        }
 378    }
 379
 380    pub fn inline_assist(
 381        workspace: &mut Workspace,
 382        _: &InlineAssist,
 383        cx: &mut ViewContext<Workspace>,
 384    ) {
 385        let settings = AssistantSettings::get_global(cx);
 386        if !settings.enabled {
 387            return;
 388        }
 389
 390        let Some(assistant_panel) = workspace.panel::<AssistantPanel>(cx) else {
 391            return;
 392        };
 393
 394        let Some(inline_assist_target) =
 395            Self::resolve_inline_assist_target(workspace, &assistant_panel, cx)
 396        else {
 397            return;
 398        };
 399
 400        if assistant_panel.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
 401            match inline_assist_target {
 402                InlineAssistTarget::Editor(active_editor, include_context) => {
 403                    InlineAssistant::update_global(cx, |assistant, cx| {
 404                        assistant.assist(
 405                            &active_editor,
 406                            Some(cx.view().downgrade()),
 407                            include_context.then_some(&assistant_panel),
 408                            cx,
 409                        )
 410                    })
 411                }
 412                InlineAssistTarget::Terminal(active_terminal) => {
 413                    TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 414                        assistant.assist(
 415                            &active_terminal,
 416                            Some(cx.view().downgrade()),
 417                            Some(&assistant_panel),
 418                            cx,
 419                        )
 420                    })
 421                }
 422            }
 423        } else {
 424            let assistant_panel = assistant_panel.downgrade();
 425            cx.spawn(|workspace, mut cx| async move {
 426                assistant_panel
 427                    .update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
 428                    .await?;
 429                if assistant_panel.update(&mut cx, |panel, cx| panel.is_authenticated(cx))? {
 430                    cx.update(|cx| match inline_assist_target {
 431                        InlineAssistTarget::Editor(active_editor, include_context) => {
 432                            let assistant_panel = if include_context {
 433                                assistant_panel.upgrade()
 434                            } else {
 435                                None
 436                            };
 437                            InlineAssistant::update_global(cx, |assistant, cx| {
 438                                assistant.assist(
 439                                    &active_editor,
 440                                    Some(workspace),
 441                                    assistant_panel.as_ref(),
 442                                    cx,
 443                                )
 444                            })
 445                        }
 446                        InlineAssistTarget::Terminal(active_terminal) => {
 447                            TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 448                                assistant.assist(
 449                                    &active_terminal,
 450                                    Some(workspace),
 451                                    assistant_panel.upgrade().as_ref(),
 452                                    cx,
 453                                )
 454                            })
 455                        }
 456                    })?
 457                } else {
 458                    workspace.update(&mut cx, |workspace, cx| {
 459                        workspace.focus_panel::<AssistantPanel>(cx)
 460                    })?;
 461                }
 462
 463                anyhow::Ok(())
 464            })
 465            .detach_and_log_err(cx)
 466        }
 467    }
 468
 469    fn resolve_inline_assist_target(
 470        workspace: &mut Workspace,
 471        assistant_panel: &View<AssistantPanel>,
 472        cx: &mut WindowContext,
 473    ) -> Option<InlineAssistTarget> {
 474        if let Some(terminal_panel) = workspace.panel::<TerminalPanel>(cx) {
 475            if terminal_panel
 476                .read(cx)
 477                .focus_handle(cx)
 478                .contains_focused(cx)
 479            {
 480                use feature_flags::FeatureFlagAppExt;
 481                if !cx.has_flag::<feature_flags::TerminalInlineAssist>() {
 482                    return None;
 483                }
 484
 485                if let Some(terminal_view) = terminal_panel
 486                    .read(cx)
 487                    .pane()
 488                    .read(cx)
 489                    .active_item()
 490                    .and_then(|t| t.downcast::<TerminalView>())
 491                {
 492                    return Some(InlineAssistTarget::Terminal(terminal_view));
 493                }
 494            }
 495        }
 496        let context_editor =
 497            assistant_panel
 498                .read(cx)
 499                .active_context_editor(cx)
 500                .and_then(|editor| {
 501                    let editor = &editor.read(cx).editor;
 502                    if editor.read(cx).is_focused(cx) {
 503                        Some(editor.clone())
 504                    } else {
 505                        None
 506                    }
 507                });
 508
 509        if let Some(context_editor) = context_editor {
 510            Some(InlineAssistTarget::Editor(context_editor, false))
 511        } else if let Some(workspace_editor) = workspace
 512            .active_item(cx)
 513            .and_then(|item| item.act_as::<Editor>(cx))
 514        {
 515            Some(InlineAssistTarget::Editor(workspace_editor, true))
 516        } else {
 517            None
 518        }
 519    }
 520
 521    fn new_context(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ContextEditor>> {
 522        let workspace = self.workspace.upgrade()?;
 523
 524        let editor = cx.new_view(|cx| {
 525            ContextEditor::new(
 526                self.languages.clone(),
 527                self.slash_commands.clone(),
 528                self.fs.clone(),
 529                workspace,
 530                cx,
 531            )
 532        });
 533
 534        self.show_context(editor.clone(), cx);
 535        Some(editor)
 536    }
 537
 538    fn show_context(&mut self, context_editor: View<ContextEditor>, cx: &mut ViewContext<Self>) {
 539        let focus = self.focus_handle(cx).contains_focused(cx);
 540        let prev_len = self.pane.read(cx).items_len();
 541        self.pane.update(cx, |pane, cx| {
 542            pane.add_item(Box::new(context_editor.clone()), focus, focus, None, cx)
 543        });
 544
 545        if prev_len != self.pane.read(cx).items_len() {
 546            self.subscriptions
 547                .push(cx.subscribe(&context_editor, Self::handle_context_editor_event));
 548        }
 549
 550        cx.emit(AssistantPanelEvent::ContextEdited);
 551        cx.notify();
 552    }
 553
 554    fn handle_context_editor_event(
 555        &mut self,
 556        _: View<ContextEditor>,
 557        event: &ContextEditorEvent,
 558        cx: &mut ViewContext<Self>,
 559    ) {
 560        match event {
 561            ContextEditorEvent::TabContentChanged => cx.notify(),
 562            ContextEditorEvent::Edited => cx.emit(AssistantPanelEvent::ContextEdited),
 563        }
 564    }
 565
 566    fn deploy_history(&mut self, _: &DeployHistory, cx: &mut ViewContext<Self>) {
 567        let history_item_ix = self
 568            .pane
 569            .read(cx)
 570            .items()
 571            .position(|item| item.downcast::<ContextHistory>().is_some());
 572
 573        if let Some(history_item_ix) = history_item_ix {
 574            self.pane.update(cx, |pane, cx| {
 575                pane.activate_item(history_item_ix, true, true, cx);
 576            });
 577        } else {
 578            let assistant_panel = cx.view().downgrade();
 579            let history = cx.new_view(|cx| {
 580                ContextHistory::new(self.context_store.clone(), assistant_panel, cx)
 581            });
 582            self.pane.update(cx, |pane, cx| {
 583                pane.add_item(Box::new(history), true, true, None, cx);
 584            });
 585        }
 586    }
 587
 588    fn deploy_prompt_library(&mut self, _: &DeployPromptLibrary, cx: &mut ViewContext<Self>) {
 589        open_prompt_library(self.languages.clone(), cx).detach_and_log_err(cx);
 590    }
 591
 592    fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
 593        CompletionProvider::global(cx)
 594            .reset_credentials(cx)
 595            .detach_and_log_err(cx);
 596    }
 597
 598    fn toggle_model_selector(&mut self, _: &ToggleModelSelector, cx: &mut ViewContext<Self>) {
 599        self.model_selector_menu_handle.toggle(cx);
 600    }
 601
 602    fn active_context_editor(&self, cx: &AppContext) -> Option<View<ContextEditor>> {
 603        self.pane
 604            .read(cx)
 605            .active_item()?
 606            .downcast::<ContextEditor>()
 607    }
 608
 609    pub fn active_context(&self, cx: &AppContext) -> Option<Model<Context>> {
 610        Some(self.active_context_editor(cx)?.read(cx).context.clone())
 611    }
 612
 613    fn open_context(&mut self, path: PathBuf, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
 614        let existing_context = self.pane.read(cx).items().find_map(|item| {
 615            item.downcast::<ContextEditor>()
 616                .filter(|editor| editor.read(cx).context.read(cx).path.as_ref() == Some(&path))
 617        });
 618        if let Some(existing_context) = existing_context {
 619            return cx.spawn(|this, mut cx| async move {
 620                this.update(&mut cx, |this, cx| this.show_context(existing_context, cx))
 621            });
 622        }
 623
 624        let saved_context = self.context_store.read(cx).load(path.clone(), cx);
 625        let fs = self.fs.clone();
 626        let workspace = self.workspace.clone();
 627        let slash_commands = self.slash_commands.clone();
 628        let languages = self.languages.clone();
 629        let telemetry = self.telemetry.clone();
 630
 631        let lsp_adapter_delegate = workspace
 632            .update(cx, |workspace, cx| {
 633                make_lsp_adapter_delegate(workspace.project(), cx).log_err()
 634            })
 635            .log_err()
 636            .flatten();
 637
 638        cx.spawn(|this, mut cx| async move {
 639            let saved_context = saved_context.await?;
 640            let context = Context::deserialize(
 641                saved_context,
 642                path,
 643                languages,
 644                slash_commands,
 645                Some(telemetry),
 646                &mut cx,
 647            )
 648            .await?;
 649
 650            this.update(&mut cx, |this, cx| {
 651                let workspace = workspace
 652                    .upgrade()
 653                    .ok_or_else(|| anyhow!("workspace dropped"))?;
 654                let editor = cx.new_view(|cx| {
 655                    ContextEditor::for_context(context, fs, workspace, lsp_adapter_delegate, cx)
 656                });
 657                this.show_context(editor, cx);
 658                anyhow::Ok(())
 659            })??;
 660            Ok(())
 661        })
 662    }
 663
 664    fn is_authenticated(&mut self, cx: &mut ViewContext<Self>) -> bool {
 665        CompletionProvider::global(cx).is_authenticated()
 666    }
 667
 668    fn authenticate(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
 669        cx.update_global::<CompletionProvider, _>(|provider, cx| provider.authenticate(cx))
 670    }
 671
 672    fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 673        let mut registrar = DivRegistrar::new(
 674            |panel, cx| {
 675                panel
 676                    .pane
 677                    .read(cx)
 678                    .toolbar()
 679                    .read(cx)
 680                    .item_of_type::<BufferSearchBar>()
 681            },
 682            cx,
 683        );
 684        BufferSearchBar::register(&mut registrar);
 685        let registrar = registrar.into_div();
 686
 687        v_flex()
 688            .key_context("AssistantPanel")
 689            .size_full()
 690            .on_action(cx.listener(|this, _: &workspace::NewFile, cx| {
 691                this.new_context(cx);
 692            }))
 693            .on_action(cx.listener(AssistantPanel::deploy_history))
 694            .on_action(cx.listener(AssistantPanel::deploy_prompt_library))
 695            .on_action(cx.listener(AssistantPanel::reset_credentials))
 696            .on_action(cx.listener(AssistantPanel::toggle_model_selector))
 697            .child(registrar.size_full().child(self.pane.clone()))
 698    }
 699}
 700
 701impl Render for AssistantPanel {
 702    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 703        if let Some(authentication_prompt) = self.authentication_prompt.as_ref() {
 704            authentication_prompt.clone().into_any()
 705        } else {
 706            self.render_signed_in(cx).into_any_element()
 707        }
 708    }
 709}
 710
 711impl Panel for AssistantPanel {
 712    fn persistent_name() -> &'static str {
 713        "AssistantPanel"
 714    }
 715
 716    fn position(&self, cx: &WindowContext) -> DockPosition {
 717        match AssistantSettings::get_global(cx).dock {
 718            AssistantDockPosition::Left => DockPosition::Left,
 719            AssistantDockPosition::Bottom => DockPosition::Bottom,
 720            AssistantDockPosition::Right => DockPosition::Right,
 721        }
 722    }
 723
 724    fn position_is_valid(&self, _: DockPosition) -> bool {
 725        true
 726    }
 727
 728    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
 729        settings::update_settings_file::<AssistantSettings>(self.fs.clone(), cx, move |settings| {
 730            let dock = match position {
 731                DockPosition::Left => AssistantDockPosition::Left,
 732                DockPosition::Bottom => AssistantDockPosition::Bottom,
 733                DockPosition::Right => AssistantDockPosition::Right,
 734            };
 735            settings.set_dock(dock);
 736        });
 737    }
 738
 739    fn size(&self, cx: &WindowContext) -> Pixels {
 740        let settings = AssistantSettings::get_global(cx);
 741        match self.position(cx) {
 742            DockPosition::Left | DockPosition::Right => {
 743                self.width.unwrap_or(settings.default_width)
 744            }
 745            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
 746        }
 747    }
 748
 749    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
 750        match self.position(cx) {
 751            DockPosition::Left | DockPosition::Right => self.width = size,
 752            DockPosition::Bottom => self.height = size,
 753        }
 754        cx.notify();
 755    }
 756
 757    fn is_zoomed(&self, cx: &WindowContext) -> bool {
 758        self.pane.read(cx).is_zoomed()
 759    }
 760
 761    fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
 762        self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
 763    }
 764
 765    fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
 766        if active {
 767            let load_credentials = self.authenticate(cx);
 768            cx.spawn(|this, mut cx| async move {
 769                load_credentials.await?;
 770                this.update(&mut cx, |this, cx| {
 771                    if this.is_authenticated(cx) && this.active_context_editor(cx).is_none() {
 772                        this.new_context(cx);
 773                    }
 774                })
 775            })
 776            .detach_and_log_err(cx);
 777        }
 778    }
 779
 780    fn icon(&self, cx: &WindowContext) -> Option<IconName> {
 781        let settings = AssistantSettings::get_global(cx);
 782        if !settings.enabled || !settings.button {
 783            return None;
 784        }
 785
 786        Some(IconName::ZedAssistant)
 787    }
 788
 789    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
 790        Some("Assistant Panel")
 791    }
 792
 793    fn toggle_action(&self) -> Box<dyn Action> {
 794        Box::new(ToggleFocus)
 795    }
 796}
 797
 798impl EventEmitter<PanelEvent> for AssistantPanel {}
 799impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
 800
 801impl FocusableView for AssistantPanel {
 802    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
 803        self.pane.focus_handle(cx)
 804    }
 805}
 806
 807#[derive(Clone)]
 808enum ContextEvent {
 809    MessagesEdited,
 810    SummaryChanged,
 811    EditSuggestionsChanged,
 812    StreamedCompletion,
 813    PendingSlashCommandsUpdated {
 814        removed: Vec<Range<language::Anchor>>,
 815        updated: Vec<PendingSlashCommand>,
 816    },
 817    SlashCommandFinished {
 818        output_range: Range<language::Anchor>,
 819        sections: Vec<SlashCommandOutputSection<language::Anchor>>,
 820        run_commands_in_output: bool,
 821    },
 822}
 823
 824#[derive(Default)]
 825struct Summary {
 826    text: String,
 827    done: bool,
 828}
 829
 830pub struct Context {
 831    id: Option<String>,
 832    buffer: Model<Buffer>,
 833    edit_suggestions: Vec<EditSuggestion>,
 834    pending_slash_commands: Vec<PendingSlashCommand>,
 835    edits_since_last_slash_command_parse: language::Subscription,
 836    slash_command_output_sections: Vec<SlashCommandOutputSection<language::Anchor>>,
 837    message_anchors: Vec<MessageAnchor>,
 838    messages_metadata: HashMap<MessageId, MessageMetadata>,
 839    next_message_id: MessageId,
 840    summary: Option<Summary>,
 841    pending_summary: Task<Option<()>>,
 842    completion_count: usize,
 843    pending_completions: Vec<PendingCompletion>,
 844    token_count: Option<usize>,
 845    pending_token_count: Task<Option<()>>,
 846    pending_edit_suggestion_parse: Option<Task<()>>,
 847    pending_save: Task<Result<()>>,
 848    path: Option<PathBuf>,
 849    _subscriptions: Vec<Subscription>,
 850    telemetry: Option<Arc<Telemetry>>,
 851    slash_command_registry: Arc<SlashCommandRegistry>,
 852    language_registry: Arc<LanguageRegistry>,
 853}
 854
 855impl EventEmitter<ContextEvent> for Context {}
 856
 857impl Context {
 858    fn new(
 859        language_registry: Arc<LanguageRegistry>,
 860        slash_command_registry: Arc<SlashCommandRegistry>,
 861        telemetry: Option<Arc<Telemetry>>,
 862        cx: &mut ModelContext<Self>,
 863    ) -> Self {
 864        let buffer = cx.new_model(|cx| {
 865            let mut buffer = Buffer::local("", cx);
 866            buffer.set_language_registry(language_registry.clone());
 867            buffer
 868        });
 869        let edits_since_last_slash_command_parse =
 870            buffer.update(cx, |buffer, _| buffer.subscribe());
 871        let mut this = Self {
 872            id: Some(Uuid::new_v4().to_string()),
 873            message_anchors: Default::default(),
 874            messages_metadata: Default::default(),
 875            next_message_id: Default::default(),
 876            edit_suggestions: Vec::new(),
 877            pending_slash_commands: Vec::new(),
 878            slash_command_output_sections: Vec::new(),
 879            edits_since_last_slash_command_parse,
 880            summary: None,
 881            pending_summary: Task::ready(None),
 882            completion_count: Default::default(),
 883            pending_completions: Default::default(),
 884            token_count: None,
 885            pending_token_count: Task::ready(None),
 886            pending_edit_suggestion_parse: None,
 887            _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
 888            pending_save: Task::ready(Ok(())),
 889            path: None,
 890            buffer,
 891            telemetry,
 892            language_registry,
 893            slash_command_registry,
 894        };
 895
 896        let message = MessageAnchor {
 897            id: MessageId(post_inc(&mut this.next_message_id.0)),
 898            start: language::Anchor::MIN,
 899        };
 900        this.message_anchors.push(message.clone());
 901        this.messages_metadata.insert(
 902            message.id,
 903            MessageMetadata {
 904                role: Role::User,
 905                status: MessageStatus::Done,
 906            },
 907        );
 908
 909        this.set_language(cx);
 910        this.count_remaining_tokens(cx);
 911        this
 912    }
 913
 914    fn serialize(&self, cx: &AppContext) -> SavedContext {
 915        let buffer = self.buffer.read(cx);
 916        SavedContext {
 917            id: self.id.clone(),
 918            zed: "context".into(),
 919            version: SavedContext::VERSION.into(),
 920            text: buffer.text(),
 921            message_metadata: self.messages_metadata.clone(),
 922            messages: self
 923                .messages(cx)
 924                .map(|message| SavedMessage {
 925                    id: message.id,
 926                    start: message.offset_range.start,
 927                })
 928                .collect(),
 929            summary: self
 930                .summary
 931                .as_ref()
 932                .map(|summary| summary.text.clone())
 933                .unwrap_or_default(),
 934            slash_command_output_sections: self
 935                .slash_command_output_sections
 936                .iter()
 937                .filter_map(|section| {
 938                    let range = section.range.to_offset(buffer);
 939                    if section.range.start.is_valid(buffer) && !range.is_empty() {
 940                        Some(SlashCommandOutputSection {
 941                            range,
 942                            icon: section.icon,
 943                            label: section.label.clone(),
 944                        })
 945                    } else {
 946                        None
 947                    }
 948                })
 949                .collect(),
 950        }
 951    }
 952
 953    #[allow(clippy::too_many_arguments)]
 954    async fn deserialize(
 955        saved_context: SavedContext,
 956        path: PathBuf,
 957        language_registry: Arc<LanguageRegistry>,
 958        slash_command_registry: Arc<SlashCommandRegistry>,
 959        telemetry: Option<Arc<Telemetry>>,
 960        cx: &mut AsyncAppContext,
 961    ) -> Result<Model<Self>> {
 962        let id = match saved_context.id {
 963            Some(id) => Some(id),
 964            None => Some(Uuid::new_v4().to_string()),
 965        };
 966
 967        let markdown = language_registry.language_for_name("Markdown");
 968        let mut message_anchors = Vec::new();
 969        let mut next_message_id = MessageId(0);
 970        let buffer = cx.new_model(|cx| {
 971            let mut buffer = Buffer::local(saved_context.text, cx);
 972            for message in saved_context.messages {
 973                message_anchors.push(MessageAnchor {
 974                    id: message.id,
 975                    start: buffer.anchor_before(message.start),
 976                });
 977                next_message_id = cmp::max(next_message_id, MessageId(message.id.0 + 1));
 978            }
 979            buffer.set_language_registry(language_registry.clone());
 980            cx.spawn(|buffer, mut cx| async move {
 981                let markdown = markdown.await?;
 982                buffer.update(&mut cx, |buffer: &mut Buffer, cx| {
 983                    buffer.set_language(Some(markdown), cx)
 984                })?;
 985                anyhow::Ok(())
 986            })
 987            .detach_and_log_err(cx);
 988            buffer
 989        })?;
 990
 991        cx.new_model(move |cx| {
 992            let edits_since_last_slash_command_parse =
 993                buffer.update(cx, |buffer, _| buffer.subscribe());
 994            let mut this = Self {
 995                id,
 996                message_anchors,
 997                messages_metadata: saved_context.message_metadata,
 998                next_message_id,
 999                edit_suggestions: Vec::new(),
1000                pending_slash_commands: Vec::new(),
1001                slash_command_output_sections: saved_context
1002                    .slash_command_output_sections
1003                    .into_iter()
1004                    .map(|section| {
1005                        let buffer = buffer.read(cx);
1006                        SlashCommandOutputSection {
1007                            range: buffer.anchor_after(section.range.start)
1008                                ..buffer.anchor_before(section.range.end),
1009                            icon: section.icon,
1010                            label: section.label,
1011                        }
1012                    })
1013                    .collect(),
1014                edits_since_last_slash_command_parse,
1015                summary: Some(Summary {
1016                    text: saved_context.summary,
1017                    done: true,
1018                }),
1019                pending_summary: Task::ready(None),
1020                completion_count: Default::default(),
1021                pending_completions: Default::default(),
1022                token_count: None,
1023                pending_edit_suggestion_parse: None,
1024                pending_token_count: Task::ready(None),
1025                _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
1026                pending_save: Task::ready(Ok(())),
1027                path: Some(path),
1028                buffer,
1029                telemetry,
1030                language_registry,
1031                slash_command_registry,
1032            };
1033            this.set_language(cx);
1034            this.reparse_edit_suggestions(cx);
1035            this.count_remaining_tokens(cx);
1036            this
1037        })
1038    }
1039
1040    fn set_language(&mut self, cx: &mut ModelContext<Self>) {
1041        let markdown = self.language_registry.language_for_name("Markdown");
1042        cx.spawn(|this, mut cx| async move {
1043            let markdown = markdown.await?;
1044            this.update(&mut cx, |this, cx| {
1045                this.buffer
1046                    .update(cx, |buffer, cx| buffer.set_language(Some(markdown), cx));
1047            })
1048        })
1049        .detach_and_log_err(cx);
1050    }
1051
1052    fn handle_buffer_event(
1053        &mut self,
1054        _: Model<Buffer>,
1055        event: &language::Event,
1056        cx: &mut ModelContext<Self>,
1057    ) {
1058        if *event == language::Event::Edited {
1059            self.count_remaining_tokens(cx);
1060            self.reparse_edit_suggestions(cx);
1061            self.reparse_slash_commands(cx);
1062            cx.emit(ContextEvent::MessagesEdited);
1063        }
1064    }
1065
1066    pub(crate) fn token_count(&self) -> Option<usize> {
1067        self.token_count
1068    }
1069
1070    pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
1071        let request = self.to_completion_request(cx);
1072        self.pending_token_count = cx.spawn(|this, mut cx| {
1073            async move {
1074                cx.background_executor()
1075                    .timer(Duration::from_millis(200))
1076                    .await;
1077
1078                let token_count = cx
1079                    .update(|cx| CompletionProvider::global(cx).count_tokens(request, cx))?
1080                    .await?;
1081
1082                this.update(&mut cx, |this, cx| {
1083                    this.token_count = Some(token_count);
1084                    cx.notify()
1085                })?;
1086                anyhow::Ok(())
1087            }
1088            .log_err()
1089        });
1090    }
1091
1092    fn reparse_slash_commands(&mut self, cx: &mut ModelContext<Self>) {
1093        let buffer = self.buffer.read(cx);
1094        let mut row_ranges = self
1095            .edits_since_last_slash_command_parse
1096            .consume()
1097            .into_iter()
1098            .map(|edit| {
1099                let start_row = buffer.offset_to_point(edit.new.start).row;
1100                let end_row = buffer.offset_to_point(edit.new.end).row + 1;
1101                start_row..end_row
1102            })
1103            .peekable();
1104
1105        let mut removed = Vec::new();
1106        let mut updated = Vec::new();
1107        while let Some(mut row_range) = row_ranges.next() {
1108            while let Some(next_row_range) = row_ranges.peek() {
1109                if row_range.end >= next_row_range.start {
1110                    row_range.end = next_row_range.end;
1111                    row_ranges.next();
1112                } else {
1113                    break;
1114                }
1115            }
1116
1117            let start = buffer.anchor_before(Point::new(row_range.start, 0));
1118            let end = buffer.anchor_after(Point::new(
1119                row_range.end - 1,
1120                buffer.line_len(row_range.end - 1),
1121            ));
1122
1123            let old_range = self.pending_command_indices_for_range(start..end, cx);
1124
1125            let mut new_commands = Vec::new();
1126            let mut lines = buffer.text_for_range(start..end).lines();
1127            let mut offset = lines.offset();
1128            while let Some(line) = lines.next() {
1129                if let Some(command_line) = SlashCommandLine::parse(line) {
1130                    let name = &line[command_line.name.clone()];
1131                    let argument = command_line.argument.as_ref().and_then(|argument| {
1132                        (!argument.is_empty()).then_some(&line[argument.clone()])
1133                    });
1134                    if let Some(command) = self.slash_command_registry.command(name) {
1135                        if !command.requires_argument() || argument.is_some() {
1136                            let start_ix = offset + command_line.name.start - 1;
1137                            let end_ix = offset
1138                                + command_line
1139                                    .argument
1140                                    .map_or(command_line.name.end, |argument| argument.end);
1141                            let source_range =
1142                                buffer.anchor_after(start_ix)..buffer.anchor_after(end_ix);
1143                            let pending_command = PendingSlashCommand {
1144                                name: name.to_string(),
1145                                argument: argument.map(ToString::to_string),
1146                                source_range,
1147                                status: PendingSlashCommandStatus::Idle,
1148                            };
1149                            updated.push(pending_command.clone());
1150                            new_commands.push(pending_command);
1151                        }
1152                    }
1153                }
1154
1155                offset = lines.offset();
1156            }
1157
1158            let removed_commands = self.pending_slash_commands.splice(old_range, new_commands);
1159            removed.extend(removed_commands.map(|command| command.source_range));
1160        }
1161
1162        if !updated.is_empty() || !removed.is_empty() {
1163            cx.emit(ContextEvent::PendingSlashCommandsUpdated { removed, updated });
1164        }
1165    }
1166
1167    fn reparse_edit_suggestions(&mut self, cx: &mut ModelContext<Self>) {
1168        self.pending_edit_suggestion_parse = Some(cx.spawn(|this, mut cx| async move {
1169            cx.background_executor()
1170                .timer(Duration::from_millis(200))
1171                .await;
1172
1173            this.update(&mut cx, |this, cx| {
1174                this.reparse_edit_suggestions_in_range(0..this.buffer.read(cx).len(), cx);
1175            })
1176            .ok();
1177        }));
1178    }
1179
1180    fn reparse_edit_suggestions_in_range(
1181        &mut self,
1182        range: Range<usize>,
1183        cx: &mut ModelContext<Self>,
1184    ) {
1185        self.buffer.update(cx, |buffer, _| {
1186            let range_start = buffer.anchor_before(range.start);
1187            let range_end = buffer.anchor_after(range.end);
1188            let start_ix = self
1189                .edit_suggestions
1190                .binary_search_by(|probe| {
1191                    probe
1192                        .source_range
1193                        .end
1194                        .cmp(&range_start, buffer)
1195                        .then(Ordering::Greater)
1196                })
1197                .unwrap_err();
1198            let end_ix = self
1199                .edit_suggestions
1200                .binary_search_by(|probe| {
1201                    probe
1202                        .source_range
1203                        .start
1204                        .cmp(&range_end, buffer)
1205                        .then(Ordering::Less)
1206                })
1207                .unwrap_err();
1208
1209            let mut new_edit_suggestions = Vec::new();
1210            let mut message_lines = buffer.as_rope().chunks_in_range(range).lines();
1211            while let Some(suggestion) = parse_next_edit_suggestion(&mut message_lines) {
1212                let start_anchor = buffer.anchor_after(suggestion.outer_range.start);
1213                let end_anchor = buffer.anchor_before(suggestion.outer_range.end);
1214                new_edit_suggestions.push(EditSuggestion {
1215                    source_range: start_anchor..end_anchor,
1216                    full_path: suggestion.path,
1217                });
1218            }
1219            self.edit_suggestions
1220                .splice(start_ix..end_ix, new_edit_suggestions);
1221        });
1222        cx.emit(ContextEvent::EditSuggestionsChanged);
1223        cx.notify();
1224    }
1225
1226    fn pending_command_for_position(
1227        &mut self,
1228        position: language::Anchor,
1229        cx: &mut ModelContext<Self>,
1230    ) -> Option<&mut PendingSlashCommand> {
1231        let buffer = self.buffer.read(cx);
1232        match self
1233            .pending_slash_commands
1234            .binary_search_by(|probe| probe.source_range.end.cmp(&position, buffer))
1235        {
1236            Ok(ix) => Some(&mut self.pending_slash_commands[ix]),
1237            Err(ix) => {
1238                let cmd = self.pending_slash_commands.get_mut(ix)?;
1239                if position.cmp(&cmd.source_range.start, buffer).is_ge()
1240                    && position.cmp(&cmd.source_range.end, buffer).is_le()
1241                {
1242                    Some(cmd)
1243                } else {
1244                    None
1245                }
1246            }
1247        }
1248    }
1249
1250    fn pending_commands_for_range(
1251        &self,
1252        range: Range<language::Anchor>,
1253        cx: &AppContext,
1254    ) -> &[PendingSlashCommand] {
1255        let range = self.pending_command_indices_for_range(range, cx);
1256        &self.pending_slash_commands[range]
1257    }
1258
1259    fn pending_command_indices_for_range(
1260        &self,
1261        range: Range<language::Anchor>,
1262        cx: &AppContext,
1263    ) -> Range<usize> {
1264        let buffer = self.buffer.read(cx);
1265        let start_ix = match self
1266            .pending_slash_commands
1267            .binary_search_by(|probe| probe.source_range.end.cmp(&range.start, &buffer))
1268        {
1269            Ok(ix) | Err(ix) => ix,
1270        };
1271        let end_ix = match self
1272            .pending_slash_commands
1273            .binary_search_by(|probe| probe.source_range.start.cmp(&range.end, &buffer))
1274        {
1275            Ok(ix) => ix + 1,
1276            Err(ix) => ix,
1277        };
1278        start_ix..end_ix
1279    }
1280
1281    fn insert_command_output(
1282        &mut self,
1283        command_range: Range<language::Anchor>,
1284        output: Task<Result<SlashCommandOutput>>,
1285        insert_trailing_newline: bool,
1286        cx: &mut ModelContext<Self>,
1287    ) {
1288        self.reparse_slash_commands(cx);
1289
1290        let insert_output_task = cx.spawn(|this, mut cx| {
1291            let command_range = command_range.clone();
1292            async move {
1293                let output = output.await;
1294                this.update(&mut cx, |this, cx| match output {
1295                    Ok(mut output) => {
1296                        if insert_trailing_newline {
1297                            output.text.push('\n');
1298                        }
1299
1300                        let event = this.buffer.update(cx, |buffer, cx| {
1301                            let start = command_range.start.to_offset(buffer);
1302                            let old_end = command_range.end.to_offset(buffer);
1303                            let new_end = start + output.text.len();
1304                            buffer.edit([(start..old_end, output.text)], None, cx);
1305
1306                            let mut sections = output
1307                                .sections
1308                                .into_iter()
1309                                .map(|section| SlashCommandOutputSection {
1310                                    range: buffer.anchor_after(start + section.range.start)
1311                                        ..buffer.anchor_before(start + section.range.end),
1312                                    icon: section.icon,
1313                                    label: section.label,
1314                                })
1315                                .collect::<Vec<_>>();
1316                            sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
1317
1318                            this.slash_command_output_sections
1319                                .extend(sections.iter().cloned());
1320                            this.slash_command_output_sections
1321                                .sort_by(|a, b| a.range.cmp(&b.range, buffer));
1322
1323                            ContextEvent::SlashCommandFinished {
1324                                output_range: buffer.anchor_after(start)
1325                                    ..buffer.anchor_before(new_end),
1326                                sections,
1327                                run_commands_in_output: output.run_commands_in_text,
1328                            }
1329                        });
1330                        cx.emit(event);
1331                    }
1332                    Err(error) => {
1333                        if let Some(pending_command) =
1334                            this.pending_command_for_position(command_range.start, cx)
1335                        {
1336                            pending_command.status =
1337                                PendingSlashCommandStatus::Error(error.to_string());
1338                            cx.emit(ContextEvent::PendingSlashCommandsUpdated {
1339                                removed: vec![pending_command.source_range.clone()],
1340                                updated: vec![pending_command.clone()],
1341                            });
1342                        }
1343                    }
1344                })
1345                .ok();
1346            }
1347        });
1348
1349        if let Some(pending_command) = self.pending_command_for_position(command_range.start, cx) {
1350            pending_command.status = PendingSlashCommandStatus::Running {
1351                _task: insert_output_task.shared(),
1352            };
1353            cx.emit(ContextEvent::PendingSlashCommandsUpdated {
1354                removed: vec![pending_command.source_range.clone()],
1355                updated: vec![pending_command.clone()],
1356            });
1357        }
1358    }
1359
1360    fn completion_provider_changed(&mut self, cx: &mut ModelContext<Self>) {
1361        self.count_remaining_tokens(cx);
1362    }
1363
1364    fn assist(
1365        &mut self,
1366        selected_messages: HashSet<MessageId>,
1367        cx: &mut ModelContext<Self>,
1368    ) -> Vec<MessageAnchor> {
1369        let mut user_messages = Vec::new();
1370
1371        let last_message_id = if let Some(last_message_id) =
1372            self.message_anchors.iter().rev().find_map(|message| {
1373                message
1374                    .start
1375                    .is_valid(self.buffer.read(cx))
1376                    .then_some(message.id)
1377            }) {
1378            last_message_id
1379        } else {
1380            return Default::default();
1381        };
1382
1383        let mut should_assist = false;
1384        for selected_message_id in selected_messages {
1385            let selected_message_role =
1386                if let Some(metadata) = self.messages_metadata.get(&selected_message_id) {
1387                    metadata.role
1388                } else {
1389                    continue;
1390                };
1391
1392            if selected_message_role == Role::Assistant {
1393                if let Some(user_message) = self.insert_message_after(
1394                    selected_message_id,
1395                    Role::User,
1396                    MessageStatus::Done,
1397                    cx,
1398                ) {
1399                    user_messages.push(user_message);
1400                }
1401            } else {
1402                should_assist = true;
1403            }
1404        }
1405
1406        if should_assist {
1407            if !CompletionProvider::global(cx).is_authenticated() {
1408                log::info!("completion provider has no credentials");
1409                return Default::default();
1410            }
1411
1412            let request = self.to_completion_request(cx);
1413            let response = CompletionProvider::global(cx).complete(request, cx);
1414            let assistant_message = self
1415                .insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
1416                .unwrap();
1417
1418            // Queue up the user's next reply.
1419            let user_message = self
1420                .insert_message_after(assistant_message.id, Role::User, MessageStatus::Done, cx)
1421                .unwrap();
1422            user_messages.push(user_message);
1423
1424            let task = cx.spawn({
1425                |this, mut cx| async move {
1426                    let response = response.await;
1427                    let assistant_message_id = assistant_message.id;
1428                    let mut response_latency = None;
1429                    let stream_completion = async {
1430                        let request_start = Instant::now();
1431                        let mut messages = response.inner.await?;
1432
1433                        while let Some(message) = messages.next().await {
1434                            if response_latency.is_none() {
1435                                response_latency = Some(request_start.elapsed());
1436                            }
1437                            let text = message?;
1438
1439                            this.update(&mut cx, |this, cx| {
1440                                let message_ix = this
1441                                    .message_anchors
1442                                    .iter()
1443                                    .position(|message| message.id == assistant_message_id)?;
1444                                let message_range = this.buffer.update(cx, |buffer, cx| {
1445                                    let message_start_offset =
1446                                        this.message_anchors[message_ix].start.to_offset(buffer);
1447                                    let message_old_end_offset = this.message_anchors
1448                                        [message_ix + 1..]
1449                                        .iter()
1450                                        .find(|message| message.start.is_valid(buffer))
1451                                        .map_or(buffer.len(), |message| {
1452                                            message.start.to_offset(buffer).saturating_sub(1)
1453                                        });
1454                                    let message_new_end_offset =
1455                                        message_old_end_offset + text.len();
1456                                    buffer.edit(
1457                                        [(message_old_end_offset..message_old_end_offset, text)],
1458                                        None,
1459                                        cx,
1460                                    );
1461                                    message_start_offset..message_new_end_offset
1462                                });
1463                                this.reparse_edit_suggestions_in_range(message_range, cx);
1464                                cx.emit(ContextEvent::StreamedCompletion);
1465
1466                                Some(())
1467                            })?;
1468                            smol::future::yield_now().await;
1469                        }
1470
1471                        this.update(&mut cx, |this, cx| {
1472                            this.pending_completions
1473                                .retain(|completion| completion.id != this.completion_count);
1474                            this.summarize(cx);
1475                        })?;
1476
1477                        anyhow::Ok(())
1478                    };
1479
1480                    let result = stream_completion.await;
1481
1482                    this.update(&mut cx, |this, cx| {
1483                        if let Some(metadata) =
1484                            this.messages_metadata.get_mut(&assistant_message.id)
1485                        {
1486                            let error_message = result
1487                                .err()
1488                                .map(|error| error.to_string().trim().to_string());
1489                            if let Some(error_message) = error_message.as_ref() {
1490                                metadata.status =
1491                                    MessageStatus::Error(SharedString::from(error_message.clone()));
1492                            } else {
1493                                metadata.status = MessageStatus::Done;
1494                            }
1495
1496                            if let Some(telemetry) = this.telemetry.as_ref() {
1497                                let model = CompletionProvider::global(cx).model();
1498                                telemetry.report_assistant_event(
1499                                    this.id.clone(),
1500                                    AssistantKind::Panel,
1501                                    model.telemetry_id(),
1502                                    response_latency,
1503                                    error_message,
1504                                );
1505                            }
1506
1507                            cx.emit(ContextEvent::MessagesEdited);
1508                        }
1509                    })
1510                    .ok();
1511                }
1512            });
1513
1514            self.pending_completions.push(PendingCompletion {
1515                id: post_inc(&mut self.completion_count),
1516                _task: task,
1517            });
1518        }
1519
1520        user_messages
1521    }
1522
1523    pub fn to_completion_request(&self, cx: &AppContext) -> LanguageModelRequest {
1524        let messages = self
1525            .messages(cx)
1526            .filter(|message| matches!(message.status, MessageStatus::Done))
1527            .map(|message| message.to_request_message(self.buffer.read(cx)));
1528
1529        LanguageModelRequest {
1530            model: CompletionProvider::global(cx).model(),
1531            messages: messages.collect(),
1532            stop: vec![],
1533            temperature: 1.0,
1534        }
1535    }
1536
1537    fn cancel_last_assist(&mut self) -> bool {
1538        self.pending_completions.pop().is_some()
1539    }
1540
1541    fn cycle_message_roles(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) {
1542        for id in ids {
1543            if let Some(metadata) = self.messages_metadata.get_mut(&id) {
1544                metadata.role.cycle();
1545                cx.emit(ContextEvent::MessagesEdited);
1546                cx.notify();
1547            }
1548        }
1549    }
1550
1551    fn insert_message_after(
1552        &mut self,
1553        message_id: MessageId,
1554        role: Role,
1555        status: MessageStatus,
1556        cx: &mut ModelContext<Self>,
1557    ) -> Option<MessageAnchor> {
1558        if let Some(prev_message_ix) = self
1559            .message_anchors
1560            .iter()
1561            .position(|message| message.id == message_id)
1562        {
1563            // Find the next valid message after the one we were given.
1564            let mut next_message_ix = prev_message_ix + 1;
1565            while let Some(next_message) = self.message_anchors.get(next_message_ix) {
1566                if next_message.start.is_valid(self.buffer.read(cx)) {
1567                    break;
1568                }
1569                next_message_ix += 1;
1570            }
1571
1572            let start = self.buffer.update(cx, |buffer, cx| {
1573                let offset = self
1574                    .message_anchors
1575                    .get(next_message_ix)
1576                    .map_or(buffer.len(), |message| message.start.to_offset(buffer) - 1);
1577                buffer.edit([(offset..offset, "\n")], None, cx);
1578                buffer.anchor_before(offset + 1)
1579            });
1580            let message = MessageAnchor {
1581                id: MessageId(post_inc(&mut self.next_message_id.0)),
1582                start,
1583            };
1584            self.message_anchors
1585                .insert(next_message_ix, message.clone());
1586            self.messages_metadata
1587                .insert(message.id, MessageMetadata { role, status });
1588            cx.emit(ContextEvent::MessagesEdited);
1589            Some(message)
1590        } else {
1591            None
1592        }
1593    }
1594
1595    fn split_message(
1596        &mut self,
1597        range: Range<usize>,
1598        cx: &mut ModelContext<Self>,
1599    ) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
1600        let start_message = self.message_for_offset(range.start, cx);
1601        let end_message = self.message_for_offset(range.end, cx);
1602        if let Some((start_message, end_message)) = start_message.zip(end_message) {
1603            // Prevent splitting when range spans multiple messages.
1604            if start_message.id != end_message.id {
1605                return (None, None);
1606            }
1607
1608            let message = start_message;
1609            let role = message.role;
1610            let mut edited_buffer = false;
1611
1612            let mut suffix_start = None;
1613            if range.start > message.offset_range.start && range.end < message.offset_range.end - 1
1614            {
1615                if self.buffer.read(cx).chars_at(range.end).next() == Some('\n') {
1616                    suffix_start = Some(range.end + 1);
1617                } else if self.buffer.read(cx).reversed_chars_at(range.end).next() == Some('\n') {
1618                    suffix_start = Some(range.end);
1619                }
1620            }
1621
1622            let suffix = if let Some(suffix_start) = suffix_start {
1623                MessageAnchor {
1624                    id: MessageId(post_inc(&mut self.next_message_id.0)),
1625                    start: self.buffer.read(cx).anchor_before(suffix_start),
1626                }
1627            } else {
1628                self.buffer.update(cx, |buffer, cx| {
1629                    buffer.edit([(range.end..range.end, "\n")], None, cx);
1630                });
1631                edited_buffer = true;
1632                MessageAnchor {
1633                    id: MessageId(post_inc(&mut self.next_message_id.0)),
1634                    start: self.buffer.read(cx).anchor_before(range.end + 1),
1635                }
1636            };
1637
1638            self.message_anchors
1639                .insert(message.index_range.end + 1, suffix.clone());
1640            self.messages_metadata.insert(
1641                suffix.id,
1642                MessageMetadata {
1643                    role,
1644                    status: MessageStatus::Done,
1645                },
1646            );
1647
1648            let new_messages =
1649                if range.start == range.end || range.start == message.offset_range.start {
1650                    (None, Some(suffix))
1651                } else {
1652                    let mut prefix_end = None;
1653                    if range.start > message.offset_range.start
1654                        && range.end < message.offset_range.end - 1
1655                    {
1656                        if self.buffer.read(cx).chars_at(range.start).next() == Some('\n') {
1657                            prefix_end = Some(range.start + 1);
1658                        } else if self.buffer.read(cx).reversed_chars_at(range.start).next()
1659                            == Some('\n')
1660                        {
1661                            prefix_end = Some(range.start);
1662                        }
1663                    }
1664
1665                    let selection = if let Some(prefix_end) = prefix_end {
1666                        cx.emit(ContextEvent::MessagesEdited);
1667                        MessageAnchor {
1668                            id: MessageId(post_inc(&mut self.next_message_id.0)),
1669                            start: self.buffer.read(cx).anchor_before(prefix_end),
1670                        }
1671                    } else {
1672                        self.buffer.update(cx, |buffer, cx| {
1673                            buffer.edit([(range.start..range.start, "\n")], None, cx)
1674                        });
1675                        edited_buffer = true;
1676                        MessageAnchor {
1677                            id: MessageId(post_inc(&mut self.next_message_id.0)),
1678                            start: self.buffer.read(cx).anchor_before(range.end + 1),
1679                        }
1680                    };
1681
1682                    self.message_anchors
1683                        .insert(message.index_range.end + 1, selection.clone());
1684                    self.messages_metadata.insert(
1685                        selection.id,
1686                        MessageMetadata {
1687                            role,
1688                            status: MessageStatus::Done,
1689                        },
1690                    );
1691                    (Some(selection), Some(suffix))
1692                };
1693
1694            if !edited_buffer {
1695                cx.emit(ContextEvent::MessagesEdited);
1696            }
1697            new_messages
1698        } else {
1699            (None, None)
1700        }
1701    }
1702
1703    fn summarize(&mut self, cx: &mut ModelContext<Self>) {
1704        if self.message_anchors.len() >= 2 && self.summary.is_none() {
1705            if !CompletionProvider::global(cx).is_authenticated() {
1706                return;
1707            }
1708
1709            let messages = self
1710                .messages(cx)
1711                .map(|message| message.to_request_message(self.buffer.read(cx)))
1712                .chain(Some(LanguageModelRequestMessage {
1713                    role: Role::User,
1714                    content: "Summarize the context into a short title without punctuation.".into(),
1715                }));
1716            let request = LanguageModelRequest {
1717                model: CompletionProvider::global(cx).model(),
1718                messages: messages.collect(),
1719                stop: vec![],
1720                temperature: 1.0,
1721            };
1722
1723            let response = CompletionProvider::global(cx).complete(request, cx);
1724            self.pending_summary = cx.spawn(|this, mut cx| {
1725                async move {
1726                    let response = response.await;
1727                    let mut messages = response.inner.await?;
1728
1729                    while let Some(message) = messages.next().await {
1730                        let text = message?;
1731                        let mut lines = text.lines();
1732                        this.update(&mut cx, |this, cx| {
1733                            let summary = this.summary.get_or_insert(Default::default());
1734                            summary.text.extend(lines.next());
1735                            cx.emit(ContextEvent::SummaryChanged);
1736                        })?;
1737
1738                        // Stop if the LLM generated multiple lines.
1739                        if lines.next().is_some() {
1740                            break;
1741                        }
1742                    }
1743
1744                    this.update(&mut cx, |this, cx| {
1745                        if let Some(summary) = this.summary.as_mut() {
1746                            summary.done = true;
1747                            cx.emit(ContextEvent::SummaryChanged);
1748                        }
1749                    })?;
1750
1751                    anyhow::Ok(())
1752                }
1753                .log_err()
1754            });
1755        }
1756    }
1757
1758    fn message_for_offset(&self, offset: usize, cx: &AppContext) -> Option<Message> {
1759        self.messages_for_offsets([offset], cx).pop()
1760    }
1761
1762    fn messages_for_offsets(
1763        &self,
1764        offsets: impl IntoIterator<Item = usize>,
1765        cx: &AppContext,
1766    ) -> Vec<Message> {
1767        let mut result = Vec::new();
1768
1769        let mut messages = self.messages(cx).peekable();
1770        let mut offsets = offsets.into_iter().peekable();
1771        let mut current_message = messages.next();
1772        while let Some(offset) = offsets.next() {
1773            // Locate the message that contains the offset.
1774            while current_message.as_ref().map_or(false, |message| {
1775                !message.offset_range.contains(&offset) && messages.peek().is_some()
1776            }) {
1777                current_message = messages.next();
1778            }
1779            let Some(message) = current_message.as_ref() else {
1780                break;
1781            };
1782
1783            // Skip offsets that are in the same message.
1784            while offsets.peek().map_or(false, |offset| {
1785                message.offset_range.contains(offset) || messages.peek().is_none()
1786            }) {
1787                offsets.next();
1788            }
1789
1790            result.push(message.clone());
1791        }
1792        result
1793    }
1794
1795    fn messages<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Message> {
1796        let buffer = self.buffer.read(cx);
1797        let mut message_anchors = self.message_anchors.iter().enumerate().peekable();
1798        iter::from_fn(move || {
1799            if let Some((start_ix, message_anchor)) = message_anchors.next() {
1800                let metadata = self.messages_metadata.get(&message_anchor.id)?;
1801                let message_start = message_anchor.start.to_offset(buffer);
1802                let mut message_end = None;
1803                let mut end_ix = start_ix;
1804                while let Some((_, next_message)) = message_anchors.peek() {
1805                    if next_message.start.is_valid(buffer) {
1806                        message_end = Some(next_message.start);
1807                        break;
1808                    } else {
1809                        end_ix += 1;
1810                        message_anchors.next();
1811                    }
1812                }
1813                let message_end = message_end
1814                    .unwrap_or(language::Anchor::MAX)
1815                    .to_offset(buffer);
1816
1817                return Some(Message {
1818                    index_range: start_ix..end_ix,
1819                    offset_range: message_start..message_end,
1820                    id: message_anchor.id,
1821                    anchor: message_anchor.start,
1822                    role: metadata.role,
1823                    status: metadata.status.clone(),
1824                });
1825            }
1826            None
1827        })
1828    }
1829
1830    fn save(
1831        &mut self,
1832        debounce: Option<Duration>,
1833        fs: Arc<dyn Fs>,
1834        cx: &mut ModelContext<Context>,
1835    ) {
1836        self.pending_save = cx.spawn(|this, mut cx| async move {
1837            if let Some(debounce) = debounce {
1838                cx.background_executor().timer(debounce).await;
1839            }
1840
1841            let (old_path, summary) = this.read_with(&cx, |this, _| {
1842                let path = this.path.clone();
1843                let summary = if let Some(summary) = this.summary.as_ref() {
1844                    if summary.done {
1845                        Some(summary.text.clone())
1846                    } else {
1847                        None
1848                    }
1849                } else {
1850                    None
1851                };
1852                (path, summary)
1853            })?;
1854
1855            if let Some(summary) = summary {
1856                let context = this.read_with(&cx, |this, cx| this.serialize(cx))?;
1857                let path = if let Some(old_path) = old_path {
1858                    old_path
1859                } else {
1860                    let mut discriminant = 1;
1861                    let mut new_path;
1862                    loop {
1863                        new_path = contexts_dir().join(&format!(
1864                            "{} - {}.zed.json",
1865                            summary.trim(),
1866                            discriminant
1867                        ));
1868                        if fs.is_file(&new_path).await {
1869                            discriminant += 1;
1870                        } else {
1871                            break;
1872                        }
1873                    }
1874                    new_path
1875                };
1876
1877                fs.create_dir(contexts_dir().as_ref()).await?;
1878                fs.atomic_write(path.clone(), serde_json::to_string(&context).unwrap())
1879                    .await?;
1880                this.update(&mut cx, |this, _| this.path = Some(path))?;
1881            }
1882
1883            Ok(())
1884        });
1885    }
1886}
1887
1888#[derive(Debug)]
1889enum EditParsingState {
1890    None,
1891    InOldText {
1892        path: PathBuf,
1893        start_offset: usize,
1894        old_text_start_offset: usize,
1895    },
1896    InNewText {
1897        path: PathBuf,
1898        start_offset: usize,
1899        old_text_range: Range<usize>,
1900        new_text_start_offset: usize,
1901    },
1902}
1903
1904#[derive(Clone, Debug, PartialEq)]
1905struct EditSuggestion {
1906    source_range: Range<language::Anchor>,
1907    full_path: PathBuf,
1908}
1909
1910struct ParsedEditSuggestion {
1911    path: PathBuf,
1912    outer_range: Range<usize>,
1913    old_text_range: Range<usize>,
1914    new_text_range: Range<usize>,
1915}
1916
1917fn parse_next_edit_suggestion(lines: &mut rope::Lines) -> Option<ParsedEditSuggestion> {
1918    let mut state = EditParsingState::None;
1919    loop {
1920        let offset = lines.offset();
1921        let message_line = lines.next()?;
1922        match state {
1923            EditParsingState::None => {
1924                if let Some(rest) = message_line.strip_prefix("```edit ") {
1925                    let path = rest.trim();
1926                    if !path.is_empty() {
1927                        state = EditParsingState::InOldText {
1928                            path: PathBuf::from(path),
1929                            start_offset: offset,
1930                            old_text_start_offset: lines.offset(),
1931                        };
1932                    }
1933                }
1934            }
1935            EditParsingState::InOldText {
1936                path,
1937                start_offset,
1938                old_text_start_offset,
1939            } => {
1940                if message_line == "---" {
1941                    state = EditParsingState::InNewText {
1942                        path,
1943                        start_offset,
1944                        old_text_range: old_text_start_offset..offset,
1945                        new_text_start_offset: lines.offset(),
1946                    };
1947                } else {
1948                    state = EditParsingState::InOldText {
1949                        path,
1950                        start_offset,
1951                        old_text_start_offset,
1952                    };
1953                }
1954            }
1955            EditParsingState::InNewText {
1956                path,
1957                start_offset,
1958                old_text_range,
1959                new_text_start_offset,
1960            } => {
1961                if message_line == "```" {
1962                    return Some(ParsedEditSuggestion {
1963                        path,
1964                        outer_range: start_offset..offset + "```".len(),
1965                        old_text_range,
1966                        new_text_range: new_text_start_offset..offset,
1967                    });
1968                } else {
1969                    state = EditParsingState::InNewText {
1970                        path,
1971                        start_offset,
1972                        old_text_range,
1973                        new_text_start_offset,
1974                    };
1975                }
1976            }
1977        }
1978    }
1979}
1980
1981#[derive(Clone)]
1982struct PendingSlashCommand {
1983    name: String,
1984    argument: Option<String>,
1985    status: PendingSlashCommandStatus,
1986    source_range: Range<language::Anchor>,
1987}
1988
1989#[derive(Clone)]
1990enum PendingSlashCommandStatus {
1991    Idle,
1992    Running { _task: Shared<Task<()>> },
1993    Error(String),
1994}
1995
1996struct PendingCompletion {
1997    id: usize,
1998    _task: Task<()>,
1999}
2000
2001pub enum ContextEditorEvent {
2002    Edited,
2003    TabContentChanged,
2004}
2005
2006#[derive(Copy, Clone, Debug, PartialEq)]
2007struct ScrollPosition {
2008    offset_before_cursor: gpui::Point<f32>,
2009    cursor: Anchor,
2010}
2011
2012pub struct ContextEditor {
2013    context: Model<Context>,
2014    fs: Arc<dyn Fs>,
2015    workspace: WeakView<Workspace>,
2016    slash_command_registry: Arc<SlashCommandRegistry>,
2017    lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
2018    editor: View<Editor>,
2019    blocks: HashSet<BlockId>,
2020    scroll_position: Option<ScrollPosition>,
2021    pending_slash_command_creases: HashMap<Range<language::Anchor>, CreaseId>,
2022    pending_slash_command_blocks: HashMap<Range<language::Anchor>, BlockId>,
2023    _subscriptions: Vec<Subscription>,
2024}
2025
2026impl ContextEditor {
2027    const MAX_TAB_TITLE_LEN: usize = 16;
2028
2029    fn new(
2030        language_registry: Arc<LanguageRegistry>,
2031        slash_command_registry: Arc<SlashCommandRegistry>,
2032        fs: Arc<dyn Fs>,
2033        workspace: View<Workspace>,
2034        cx: &mut ViewContext<Self>,
2035    ) -> Self {
2036        let telemetry = workspace.read(cx).client().telemetry().clone();
2037        let project = workspace.read(cx).project().clone();
2038        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err();
2039
2040        let context = cx.new_model(|cx| {
2041            Context::new(
2042                language_registry,
2043                slash_command_registry,
2044                Some(telemetry),
2045                cx,
2046            )
2047        });
2048
2049        let mut this = Self::for_context(context, fs, workspace, lsp_adapter_delegate, cx);
2050        this.insert_default_prompt(cx);
2051        this
2052    }
2053
2054    fn for_context(
2055        context: Model<Context>,
2056        fs: Arc<dyn Fs>,
2057        workspace: View<Workspace>,
2058        lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
2059        cx: &mut ViewContext<Self>,
2060    ) -> Self {
2061        let slash_command_registry = context.read(cx).slash_command_registry.clone();
2062
2063        let completion_provider = SlashCommandCompletionProvider::new(
2064            slash_command_registry.clone(),
2065            Some(cx.view().downgrade()),
2066            Some(workspace.downgrade()),
2067        );
2068
2069        let editor = cx.new_view(|cx| {
2070            let mut editor = Editor::for_buffer(context.read(cx).buffer.clone(), None, cx);
2071            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
2072            editor.set_show_line_numbers(false, cx);
2073            editor.set_show_git_diff_gutter(false, cx);
2074            editor.set_show_code_actions(false, cx);
2075            editor.set_show_runnables(false, cx);
2076            editor.set_show_wrap_guides(false, cx);
2077            editor.set_show_indent_guides(false, cx);
2078            editor.set_completion_provider(Box::new(completion_provider));
2079            editor
2080        });
2081
2082        let _subscriptions = vec![
2083            cx.observe(&context, |_, _, cx| cx.notify()),
2084            cx.subscribe(&context, Self::handle_context_event),
2085            cx.subscribe(&editor, Self::handle_editor_event),
2086            cx.subscribe(&editor, Self::handle_editor_search_event),
2087        ];
2088
2089        let sections = context.read(cx).slash_command_output_sections.clone();
2090        let mut this = Self {
2091            context,
2092            editor,
2093            slash_command_registry,
2094            lsp_adapter_delegate,
2095            blocks: Default::default(),
2096            scroll_position: None,
2097            fs,
2098            workspace: workspace.downgrade(),
2099            pending_slash_command_creases: HashMap::default(),
2100            pending_slash_command_blocks: HashMap::default(),
2101            _subscriptions,
2102        };
2103        this.update_message_headers(cx);
2104        this.insert_slash_command_output_sections(sections, cx);
2105        this
2106    }
2107
2108    fn insert_default_prompt(&mut self, cx: &mut ViewContext<Self>) {
2109        let command_name = DefaultSlashCommand.name();
2110        self.editor.update(cx, |editor, cx| {
2111            editor.insert(&format!("/{command_name}"), cx)
2112        });
2113        self.split(&Split, cx);
2114        let command = self.context.update(cx, |context, cx| {
2115            context
2116                .messages_metadata
2117                .get_mut(&MessageId::default())
2118                .unwrap()
2119                .role = Role::System;
2120            context.reparse_slash_commands(cx);
2121            context.pending_slash_commands[0].clone()
2122        });
2123
2124        self.run_command(
2125            command.source_range,
2126            &command.name,
2127            command.argument.as_deref(),
2128            false,
2129            self.workspace.clone(),
2130            cx,
2131        );
2132    }
2133
2134    fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
2135        let cursors = self.cursors(cx);
2136
2137        let user_messages = self.context.update(cx, |context, cx| {
2138            let selected_messages = context
2139                .messages_for_offsets(cursors, cx)
2140                .into_iter()
2141                .map(|message| message.id)
2142                .collect();
2143            context.assist(selected_messages, cx)
2144        });
2145        let new_selections = user_messages
2146            .iter()
2147            .map(|message| {
2148                let cursor = message
2149                    .start
2150                    .to_offset(self.context.read(cx).buffer.read(cx));
2151                cursor..cursor
2152            })
2153            .collect::<Vec<_>>();
2154        if !new_selections.is_empty() {
2155            self.editor.update(cx, |editor, cx| {
2156                editor.change_selections(
2157                    Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
2158                    cx,
2159                    |selections| selections.select_ranges(new_selections),
2160                );
2161            });
2162            // Avoid scrolling to the new cursor position so the assistant's output is stable.
2163            cx.defer(|this, _| this.scroll_position = None);
2164        }
2165    }
2166
2167    fn cancel_last_assist(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
2168        if !self
2169            .context
2170            .update(cx, |context, _| context.cancel_last_assist())
2171        {
2172            cx.propagate();
2173        }
2174    }
2175
2176    fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
2177        let cursors = self.cursors(cx);
2178        self.context.update(cx, |context, cx| {
2179            let messages = context
2180                .messages_for_offsets(cursors, cx)
2181                .into_iter()
2182                .map(|message| message.id)
2183                .collect();
2184            context.cycle_message_roles(messages, cx)
2185        });
2186    }
2187
2188    fn cursors(&self, cx: &AppContext) -> Vec<usize> {
2189        let selections = self.editor.read(cx).selections.all::<usize>(cx);
2190        selections
2191            .into_iter()
2192            .map(|selection| selection.head())
2193            .collect()
2194    }
2195
2196    fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
2197        if let Some(command) = self.slash_command_registry.command(name) {
2198            self.editor.update(cx, |editor, cx| {
2199                editor.transact(cx, |editor, cx| {
2200                    editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel());
2201                    let snapshot = editor.buffer().read(cx).snapshot(cx);
2202                    let newest_cursor = editor.selections.newest::<Point>(cx).head();
2203                    if newest_cursor.column > 0
2204                        || snapshot
2205                            .chars_at(newest_cursor)
2206                            .next()
2207                            .map_or(false, |ch| ch != '\n')
2208                    {
2209                        editor.move_to_end_of_line(
2210                            &MoveToEndOfLine {
2211                                stop_at_soft_wraps: false,
2212                            },
2213                            cx,
2214                        );
2215                        editor.newline(&Newline, cx);
2216                    }
2217
2218                    editor.insert(&format!("/{name}"), cx);
2219                    if command.requires_argument() {
2220                        editor.insert(" ", cx);
2221                        editor.show_completions(&ShowCompletions::default(), cx);
2222                    }
2223                });
2224            });
2225            if !command.requires_argument() {
2226                self.confirm_command(&ConfirmCommand, cx);
2227            }
2228        }
2229    }
2230
2231    pub fn confirm_command(&mut self, _: &ConfirmCommand, cx: &mut ViewContext<Self>) {
2232        let selections = self.editor.read(cx).selections.disjoint_anchors();
2233        let mut commands_by_range = HashMap::default();
2234        let workspace = self.workspace.clone();
2235        self.context.update(cx, |context, cx| {
2236            context.reparse_slash_commands(cx);
2237            for selection in selections.iter() {
2238                if let Some(command) =
2239                    context.pending_command_for_position(selection.head().text_anchor, cx)
2240                {
2241                    commands_by_range
2242                        .entry(command.source_range.clone())
2243                        .or_insert_with(|| command.clone());
2244                }
2245            }
2246        });
2247
2248        if commands_by_range.is_empty() {
2249            cx.propagate();
2250        } else {
2251            for command in commands_by_range.into_values() {
2252                self.run_command(
2253                    command.source_range,
2254                    &command.name,
2255                    command.argument.as_deref(),
2256                    true,
2257                    workspace.clone(),
2258                    cx,
2259                );
2260            }
2261            cx.stop_propagation();
2262        }
2263    }
2264
2265    pub fn run_command(
2266        &mut self,
2267        command_range: Range<language::Anchor>,
2268        name: &str,
2269        argument: Option<&str>,
2270        insert_trailing_newline: bool,
2271        workspace: WeakView<Workspace>,
2272        cx: &mut ViewContext<Self>,
2273    ) {
2274        if let Some(command) = self.slash_command_registry.command(name) {
2275            if let Some(lsp_adapter_delegate) = self.lsp_adapter_delegate.clone() {
2276                let argument = argument.map(ToString::to_string);
2277                let output = command.run(argument.as_deref(), workspace, lsp_adapter_delegate, cx);
2278                self.context.update(cx, |context, cx| {
2279                    context.insert_command_output(
2280                        command_range,
2281                        output,
2282                        insert_trailing_newline,
2283                        cx,
2284                    )
2285                });
2286            }
2287        }
2288    }
2289
2290    fn handle_context_event(
2291        &mut self,
2292        _: Model<Context>,
2293        event: &ContextEvent,
2294        cx: &mut ViewContext<Self>,
2295    ) {
2296        let context_editor = cx.view().downgrade();
2297
2298        match event {
2299            ContextEvent::MessagesEdited => {
2300                self.update_message_headers(cx);
2301                self.context.update(cx, |context, cx| {
2302                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
2303                });
2304            }
2305            ContextEvent::EditSuggestionsChanged => {
2306                self.editor.update(cx, |editor, cx| {
2307                    let buffer = editor.buffer().read(cx).snapshot(cx);
2308                    let excerpt_id = *buffer.as_singleton().unwrap().0;
2309                    let context = self.context.read(cx);
2310                    let highlighted_rows = context
2311                        .edit_suggestions
2312                        .iter()
2313                        .map(|suggestion| {
2314                            let start = buffer
2315                                .anchor_in_excerpt(excerpt_id, suggestion.source_range.start)
2316                                .unwrap();
2317                            let end = buffer
2318                                .anchor_in_excerpt(excerpt_id, suggestion.source_range.end)
2319                                .unwrap();
2320                            start..=end
2321                        })
2322                        .collect::<Vec<_>>();
2323
2324                    editor.clear_row_highlights::<EditSuggestion>();
2325                    for range in highlighted_rows {
2326                        editor.highlight_rows::<EditSuggestion>(
2327                            range,
2328                            Some(
2329                                cx.theme()
2330                                    .colors()
2331                                    .editor_document_highlight_read_background,
2332                            ),
2333                            false,
2334                            cx,
2335                        );
2336                    }
2337                });
2338            }
2339            ContextEvent::SummaryChanged => {
2340                cx.emit(ContextEditorEvent::TabContentChanged);
2341                self.context.update(cx, |context, cx| {
2342                    context.save(None, self.fs.clone(), cx);
2343                });
2344            }
2345            ContextEvent::StreamedCompletion => {
2346                self.editor.update(cx, |editor, cx| {
2347                    if let Some(scroll_position) = self.scroll_position {
2348                        let snapshot = editor.snapshot(cx);
2349                        let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
2350                        let scroll_top =
2351                            cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
2352                        editor.set_scroll_position(
2353                            point(scroll_position.offset_before_cursor.x, scroll_top),
2354                            cx,
2355                        );
2356                    }
2357                });
2358            }
2359            ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
2360                self.editor.update(cx, |editor, cx| {
2361                    let buffer = editor.buffer().read(cx).snapshot(cx);
2362                    let (excerpt_id, buffer_id, _) = buffer.as_singleton().unwrap();
2363                    let excerpt_id = *excerpt_id;
2364
2365                    editor.remove_creases(
2366                        removed
2367                            .iter()
2368                            .filter_map(|range| self.pending_slash_command_creases.remove(range)),
2369                        cx,
2370                    );
2371
2372                    editor.remove_blocks(
2373                        HashSet::from_iter(
2374                            removed.iter().filter_map(|range| {
2375                                self.pending_slash_command_blocks.remove(range)
2376                            }),
2377                        ),
2378                        None,
2379                        cx,
2380                    );
2381
2382                    let crease_ids = editor.insert_creases(
2383                        updated.iter().map(|command| {
2384                            let workspace = self.workspace.clone();
2385                            let confirm_command = Arc::new({
2386                                let context_editor = context_editor.clone();
2387                                let command = command.clone();
2388                                move |cx: &mut WindowContext| {
2389                                    context_editor
2390                                        .update(cx, |context_editor, cx| {
2391                                            context_editor.run_command(
2392                                                command.source_range.clone(),
2393                                                &command.name,
2394                                                command.argument.as_deref(),
2395                                                false,
2396                                                workspace.clone(),
2397                                                cx,
2398                                            );
2399                                        })
2400                                        .ok();
2401                                }
2402                            });
2403                            let placeholder = FoldPlaceholder {
2404                                render: Arc::new(move |_, _, _| Empty.into_any()),
2405                                constrain_width: false,
2406                                merge_adjacent: false,
2407                            };
2408                            let render_toggle = {
2409                                let confirm_command = confirm_command.clone();
2410                                let command = command.clone();
2411                                move |row, _, _, _cx: &mut WindowContext| {
2412                                    render_pending_slash_command_gutter_decoration(
2413                                        row,
2414                                        &command.status,
2415                                        confirm_command.clone(),
2416                                    )
2417                                }
2418                            };
2419                            let render_trailer = {
2420                                let command = command.clone();
2421                                move |row, _unfold, cx: &mut WindowContext| {
2422                                    // TODO: In the future we should investigate how we can expose
2423                                    // this as a hook on the `SlashCommand` trait so that we don't
2424                                    // need to special-case it here.
2425                                    if command.name == DocsSlashCommand::NAME {
2426                                        return render_docs_slash_command_trailer(
2427                                            row,
2428                                            command.clone(),
2429                                            cx,
2430                                        );
2431                                    }
2432
2433                                    Empty.into_any()
2434                                }
2435                            };
2436
2437                            let start = buffer
2438                                .anchor_in_excerpt(excerpt_id, command.source_range.start)
2439                                .unwrap();
2440                            let end = buffer
2441                                .anchor_in_excerpt(excerpt_id, command.source_range.end)
2442                                .unwrap();
2443                            Crease::new(start..end, placeholder, render_toggle, render_trailer)
2444                        }),
2445                        cx,
2446                    );
2447
2448                    let block_ids = editor.insert_blocks(
2449                        updated
2450                            .iter()
2451                            .filter_map(|command| match &command.status {
2452                                PendingSlashCommandStatus::Error(error) => {
2453                                    Some((command, error.clone()))
2454                                }
2455                                _ => None,
2456                            })
2457                            .map(|(command, error_message)| BlockProperties {
2458                                style: BlockStyle::Fixed,
2459                                position: Anchor {
2460                                    buffer_id: Some(buffer_id),
2461                                    excerpt_id,
2462                                    text_anchor: command.source_range.start,
2463                                },
2464                                height: 1,
2465                                disposition: BlockDisposition::Below,
2466                                render: slash_command_error_block_renderer(error_message),
2467                            }),
2468                        None,
2469                        cx,
2470                    );
2471
2472                    self.pending_slash_command_creases.extend(
2473                        updated
2474                            .iter()
2475                            .map(|command| command.source_range.clone())
2476                            .zip(crease_ids),
2477                    );
2478
2479                    self.pending_slash_command_blocks.extend(
2480                        updated
2481                            .iter()
2482                            .map(|command| command.source_range.clone())
2483                            .zip(block_ids),
2484                    );
2485                })
2486            }
2487            ContextEvent::SlashCommandFinished {
2488                output_range,
2489                sections,
2490                run_commands_in_output,
2491            } => {
2492                self.insert_slash_command_output_sections(sections.iter().cloned(), cx);
2493
2494                if *run_commands_in_output {
2495                    let commands = self.context.update(cx, |context, cx| {
2496                        context.reparse_slash_commands(cx);
2497                        context
2498                            .pending_commands_for_range(output_range.clone(), cx)
2499                            .to_vec()
2500                    });
2501
2502                    for command in commands {
2503                        self.run_command(
2504                            command.source_range,
2505                            &command.name,
2506                            command.argument.as_deref(),
2507                            false,
2508                            self.workspace.clone(),
2509                            cx,
2510                        );
2511                    }
2512                }
2513            }
2514        }
2515    }
2516
2517    fn insert_slash_command_output_sections(
2518        &mut self,
2519        sections: impl IntoIterator<Item = SlashCommandOutputSection<language::Anchor>>,
2520        cx: &mut ViewContext<Self>,
2521    ) {
2522        self.editor.update(cx, |editor, cx| {
2523            let buffer = editor.buffer().read(cx).snapshot(cx);
2524            let excerpt_id = *buffer.as_singleton().unwrap().0;
2525            let mut buffer_rows_to_fold = BTreeSet::new();
2526            let mut creases = Vec::new();
2527            for section in sections {
2528                let start = buffer
2529                    .anchor_in_excerpt(excerpt_id, section.range.start)
2530                    .unwrap();
2531                let end = buffer
2532                    .anchor_in_excerpt(excerpt_id, section.range.end)
2533                    .unwrap();
2534                let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2535                buffer_rows_to_fold.insert(buffer_row);
2536                creases.push(Crease::new(
2537                    start..end,
2538                    FoldPlaceholder {
2539                        render: Arc::new({
2540                            let editor = cx.view().downgrade();
2541                            let icon = section.icon;
2542                            let label = section.label.clone();
2543                            move |fold_id, fold_range, _cx| {
2544                                let editor = editor.clone();
2545                                ButtonLike::new(fold_id)
2546                                    .style(ButtonStyle::Filled)
2547                                    .layer(ElevationIndex::ElevatedSurface)
2548                                    .child(Icon::new(icon))
2549                                    .child(Label::new(label.clone()).single_line())
2550                                    .on_click(move |_, cx| {
2551                                        editor
2552                                            .update(cx, |editor, cx| {
2553                                                let buffer_start = fold_range
2554                                                    .start
2555                                                    .to_point(&editor.buffer().read(cx).read(cx));
2556                                                let buffer_row = MultiBufferRow(buffer_start.row);
2557                                                editor.unfold_at(&UnfoldAt { buffer_row }, cx);
2558                                            })
2559                                            .ok();
2560                                    })
2561                                    .into_any_element()
2562                            }
2563                        }),
2564                        constrain_width: false,
2565                        merge_adjacent: false,
2566                    },
2567                    render_slash_command_output_toggle,
2568                    |_, _, _| Empty.into_any_element(),
2569                ));
2570            }
2571
2572            editor.insert_creases(creases, cx);
2573
2574            for buffer_row in buffer_rows_to_fold.into_iter().rev() {
2575                editor.fold_at(&FoldAt { buffer_row }, cx);
2576            }
2577        });
2578    }
2579
2580    fn handle_editor_event(
2581        &mut self,
2582        _: View<Editor>,
2583        event: &EditorEvent,
2584        cx: &mut ViewContext<Self>,
2585    ) {
2586        match event {
2587            EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
2588                let cursor_scroll_position = self.cursor_scroll_position(cx);
2589                if *autoscroll {
2590                    self.scroll_position = cursor_scroll_position;
2591                } else if self.scroll_position != cursor_scroll_position {
2592                    self.scroll_position = None;
2593                }
2594            }
2595            EditorEvent::SelectionsChanged { .. } => {
2596                self.scroll_position = self.cursor_scroll_position(cx);
2597            }
2598            EditorEvent::BufferEdited => cx.emit(ContextEditorEvent::Edited),
2599            _ => {}
2600        }
2601    }
2602
2603    fn handle_editor_search_event(
2604        &mut self,
2605        _: View<Editor>,
2606        event: &SearchEvent,
2607        cx: &mut ViewContext<Self>,
2608    ) {
2609        cx.emit(event.clone());
2610    }
2611
2612    fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
2613        self.editor.update(cx, |editor, cx| {
2614            let snapshot = editor.snapshot(cx);
2615            let cursor = editor.selections.newest_anchor().head();
2616            let cursor_row = cursor
2617                .to_display_point(&snapshot.display_snapshot)
2618                .row()
2619                .as_f32();
2620            let scroll_position = editor
2621                .scroll_manager
2622                .anchor()
2623                .scroll_position(&snapshot.display_snapshot);
2624
2625            let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
2626            if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
2627                Some(ScrollPosition {
2628                    cursor,
2629                    offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
2630                })
2631            } else {
2632                None
2633            }
2634        })
2635    }
2636
2637    fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
2638        self.editor.update(cx, |editor, cx| {
2639            let buffer = editor.buffer().read(cx).snapshot(cx);
2640            let excerpt_id = *buffer.as_singleton().unwrap().0;
2641            let old_blocks = std::mem::take(&mut self.blocks);
2642            let new_blocks = self
2643                .context
2644                .read(cx)
2645                .messages(cx)
2646                .map(|message| BlockProperties {
2647                    position: buffer
2648                        .anchor_in_excerpt(excerpt_id, message.anchor)
2649                        .unwrap(),
2650                    height: 2,
2651                    style: BlockStyle::Sticky,
2652                    render: Box::new({
2653                        let context = self.context.clone();
2654                        move |cx| {
2655                            let message_id = message.id;
2656                            let sender = ButtonLike::new("role")
2657                                .style(ButtonStyle::Filled)
2658                                .child(match message.role {
2659                                    Role::User => Label::new("You").color(Color::Default),
2660                                    Role::Assistant => Label::new("Assistant").color(Color::Info),
2661                                    Role::System => Label::new("System").color(Color::Warning),
2662                                })
2663                                .tooltip(|cx| {
2664                                    Tooltip::with_meta(
2665                                        "Toggle message role",
2666                                        None,
2667                                        "Available roles: You (User), Assistant, System",
2668                                        cx,
2669                                    )
2670                                })
2671                                .on_click({
2672                                    let context = context.clone();
2673                                    move |_, cx| {
2674                                        context.update(cx, |context, cx| {
2675                                            context.cycle_message_roles(
2676                                                HashSet::from_iter(Some(message_id)),
2677                                                cx,
2678                                            )
2679                                        })
2680                                    }
2681                                });
2682
2683                            h_flex()
2684                                .id(("message_header", message_id.0))
2685                                .pl(cx.gutter_dimensions.full_width())
2686                                .h_11()
2687                                .w_full()
2688                                .relative()
2689                                .gap_1()
2690                                .child(sender)
2691                                .children(
2692                                    if let MessageStatus::Error(error) = message.status.clone() {
2693                                        Some(
2694                                            div()
2695                                                .id("error")
2696                                                .tooltip(move |cx| Tooltip::text(error.clone(), cx))
2697                                                .child(Icon::new(IconName::XCircle)),
2698                                        )
2699                                    } else {
2700                                        None
2701                                    },
2702                                )
2703                                .into_any_element()
2704                        }
2705                    }),
2706                    disposition: BlockDisposition::Above,
2707                })
2708                .collect::<Vec<_>>();
2709
2710            editor.remove_blocks(old_blocks, None, cx);
2711            let ids = editor.insert_blocks(new_blocks, None, cx);
2712            self.blocks = HashSet::from_iter(ids);
2713        });
2714    }
2715
2716    fn insert_selection(
2717        workspace: &mut Workspace,
2718        _: &InsertIntoEditor,
2719        cx: &mut ViewContext<Workspace>,
2720    ) {
2721        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2722            return;
2723        };
2724        let Some(context_editor_view) = panel.read(cx).active_context_editor(cx) else {
2725            return;
2726        };
2727        let Some(active_editor_view) = workspace
2728            .active_item(cx)
2729            .and_then(|item| item.act_as::<Editor>(cx))
2730        else {
2731            return;
2732        };
2733
2734        let context_editor = context_editor_view.read(cx).editor.read(cx);
2735        let anchor = context_editor.selections.newest_anchor();
2736        let text = context_editor
2737            .buffer()
2738            .read(cx)
2739            .read(cx)
2740            .text_for_range(anchor.range())
2741            .collect::<String>();
2742
2743        // If nothing is selected, don't delete the current selection; instead, be a no-op.
2744        if !text.is_empty() {
2745            active_editor_view.update(cx, |editor, cx| {
2746                editor.insert(&text, cx);
2747                editor.focus(cx);
2748            })
2749        }
2750    }
2751
2752    fn quote_selection(
2753        workspace: &mut Workspace,
2754        _: &QuoteSelection,
2755        cx: &mut ViewContext<Workspace>,
2756    ) {
2757        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2758            return;
2759        };
2760        let Some(editor) = workspace
2761            .active_item(cx)
2762            .and_then(|item| item.act_as::<Editor>(cx))
2763        else {
2764            return;
2765        };
2766
2767        let editor = editor.read(cx);
2768        let range = editor.selections.newest::<usize>(cx).range();
2769        let buffer = editor.buffer().read(cx).snapshot(cx);
2770        let start_language = buffer.language_at(range.start);
2771        let end_language = buffer.language_at(range.end);
2772        let language_name = if start_language == end_language {
2773            start_language.map(|language| language.code_fence_block_name())
2774        } else {
2775            None
2776        };
2777        let language_name = language_name.as_deref().unwrap_or("");
2778
2779        let selected_text = buffer.text_for_range(range).collect::<String>();
2780        let text = if selected_text.is_empty() {
2781            None
2782        } else {
2783            Some(if language_name == "markdown" {
2784                selected_text
2785                    .lines()
2786                    .map(|line| format!("> {}", line))
2787                    .collect::<Vec<_>>()
2788                    .join("\n")
2789            } else {
2790                format!("```{language_name}\n{selected_text}\n```")
2791            })
2792        };
2793
2794        // Activate the panel
2795        if !panel.focus_handle(cx).contains_focused(cx) {
2796            workspace.toggle_panel_focus::<AssistantPanel>(cx);
2797        }
2798
2799        if let Some(text) = text {
2800            panel.update(cx, |_, cx| {
2801                // Wait to create a new context until the workspace is no longer
2802                // being updated.
2803                cx.defer(move |panel, cx| {
2804                    if let Some(context) = panel
2805                        .active_context_editor(cx)
2806                        .or_else(|| panel.new_context(cx))
2807                    {
2808                        context.update(cx, |context, cx| {
2809                            context
2810                                .editor
2811                                .update(cx, |editor, cx| editor.insert(&text, cx))
2812                        });
2813                    };
2814                });
2815            });
2816        }
2817    }
2818
2819    fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
2820        let editor = self.editor.read(cx);
2821        let context = self.context.read(cx);
2822        if editor.selections.count() == 1 {
2823            let selection = editor.selections.newest::<usize>(cx);
2824            let mut copied_text = String::new();
2825            let mut spanned_messages = 0;
2826            for message in context.messages(cx) {
2827                if message.offset_range.start >= selection.range().end {
2828                    break;
2829                } else if message.offset_range.end >= selection.range().start {
2830                    let range = cmp::max(message.offset_range.start, selection.range().start)
2831                        ..cmp::min(message.offset_range.end, selection.range().end);
2832                    if !range.is_empty() {
2833                        spanned_messages += 1;
2834                        write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
2835                        for chunk in context.buffer.read(cx).text_for_range(range) {
2836                            copied_text.push_str(chunk);
2837                        }
2838                        copied_text.push('\n');
2839                    }
2840                }
2841            }
2842
2843            if spanned_messages > 1 {
2844                cx.write_to_clipboard(ClipboardItem::new(copied_text));
2845                return;
2846            }
2847        }
2848
2849        cx.propagate();
2850    }
2851
2852    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
2853        self.context.update(cx, |context, cx| {
2854            let selections = self.editor.read(cx).selections.disjoint_anchors();
2855            for selection in selections.as_ref() {
2856                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
2857                let range = selection
2858                    .map(|endpoint| endpoint.to_offset(&buffer))
2859                    .range();
2860                context.split_message(range, cx);
2861            }
2862        });
2863    }
2864
2865    fn apply_edit(&mut self, _: &ApplyEdit, cx: &mut ViewContext<Self>) {
2866        let Some(workspace) = self.workspace.upgrade() else {
2867            return;
2868        };
2869        let project = workspace.read(cx).project().clone();
2870
2871        struct Edit {
2872            old_text: String,
2873            new_text: String,
2874        }
2875
2876        let context = self.context.read(cx);
2877        let context_buffer = context.buffer.read(cx);
2878        let context_buffer_snapshot = context_buffer.snapshot();
2879
2880        let selections = self.editor.read(cx).selections.disjoint_anchors();
2881        let mut selections = selections.iter().peekable();
2882        let selected_suggestions = context
2883            .edit_suggestions
2884            .iter()
2885            .filter(|suggestion| {
2886                while let Some(selection) = selections.peek() {
2887                    if selection
2888                        .end
2889                        .text_anchor
2890                        .cmp(&suggestion.source_range.start, context_buffer)
2891                        .is_lt()
2892                    {
2893                        selections.next();
2894                        continue;
2895                    }
2896                    if selection
2897                        .start
2898                        .text_anchor
2899                        .cmp(&suggestion.source_range.end, context_buffer)
2900                        .is_gt()
2901                    {
2902                        break;
2903                    }
2904                    return true;
2905                }
2906                false
2907            })
2908            .cloned()
2909            .collect::<Vec<_>>();
2910
2911        let mut opened_buffers: HashMap<PathBuf, Task<Result<Model<Buffer>>>> = HashMap::default();
2912        project.update(cx, |project, cx| {
2913            for suggestion in &selected_suggestions {
2914                opened_buffers
2915                    .entry(suggestion.full_path.clone())
2916                    .or_insert_with(|| {
2917                        project.open_buffer_for_full_path(&suggestion.full_path, cx)
2918                    });
2919            }
2920        });
2921
2922        cx.spawn(|this, mut cx| async move {
2923            let mut buffers_by_full_path = HashMap::default();
2924            for (full_path, buffer) in opened_buffers {
2925                if let Some(buffer) = buffer.await.log_err() {
2926                    buffers_by_full_path.insert(full_path, buffer);
2927                }
2928            }
2929
2930            let mut suggestions_by_buffer = HashMap::default();
2931            cx.update(|cx| {
2932                for suggestion in selected_suggestions {
2933                    if let Some(buffer) = buffers_by_full_path.get(&suggestion.full_path) {
2934                        let (_, edits) = suggestions_by_buffer
2935                            .entry(buffer.clone())
2936                            .or_insert_with(|| (buffer.read(cx).snapshot(), Vec::new()));
2937
2938                        let mut lines = context_buffer_snapshot
2939                            .as_rope()
2940                            .chunks_in_range(
2941                                suggestion.source_range.to_offset(&context_buffer_snapshot),
2942                            )
2943                            .lines();
2944                        if let Some(suggestion) = parse_next_edit_suggestion(&mut lines) {
2945                            let old_text = context_buffer_snapshot
2946                                .text_for_range(suggestion.old_text_range)
2947                                .collect();
2948                            let new_text = context_buffer_snapshot
2949                                .text_for_range(suggestion.new_text_range)
2950                                .collect();
2951                            edits.push(Edit { old_text, new_text });
2952                        }
2953                    }
2954                }
2955            })?;
2956
2957            let edits_by_buffer = cx
2958                .background_executor()
2959                .spawn(async move {
2960                    let mut result = HashMap::default();
2961                    for (buffer, (snapshot, suggestions)) in suggestions_by_buffer {
2962                        let edits =
2963                            result
2964                                .entry(buffer)
2965                                .or_insert(Vec::<(Range<language::Anchor>, _)>::new());
2966                        for suggestion in suggestions {
2967                            if let Some(range) =
2968                                fuzzy_search_lines(snapshot.as_rope(), &suggestion.old_text)
2969                            {
2970                                let edit_start = snapshot.anchor_after(range.start);
2971                                let edit_end = snapshot.anchor_before(range.end);
2972                                if let Err(ix) = edits.binary_search_by(|(range, _)| {
2973                                    range.start.cmp(&edit_start, &snapshot)
2974                                }) {
2975                                    edits.insert(
2976                                        ix,
2977                                        (edit_start..edit_end, suggestion.new_text.clone()),
2978                                    );
2979                                }
2980                            } else {
2981                                log::info!(
2982                                    "assistant edit did not match any text in buffer {:?}",
2983                                    &suggestion.old_text
2984                                );
2985                            }
2986                        }
2987                    }
2988                    result
2989                })
2990                .await;
2991
2992            let mut project_transaction = ProjectTransaction::default();
2993            let (editor, workspace, title) = this.update(&mut cx, |this, cx| {
2994                for (buffer_handle, edits) in edits_by_buffer {
2995                    buffer_handle.update(cx, |buffer, cx| {
2996                        buffer.start_transaction();
2997                        buffer.edit(
2998                            edits,
2999                            Some(AutoindentMode::Block {
3000                                original_indent_columns: Vec::new(),
3001                            }),
3002                            cx,
3003                        );
3004                        buffer.end_transaction(cx);
3005                        if let Some(transaction) = buffer.finalize_last_transaction() {
3006                            project_transaction
3007                                .0
3008                                .insert(buffer_handle.clone(), transaction.clone());
3009                        }
3010                    });
3011                }
3012
3013                (
3014                    this.editor.downgrade(),
3015                    this.workspace.clone(),
3016                    this.title(cx),
3017                )
3018            })?;
3019
3020            Editor::open_project_transaction(
3021                &editor,
3022                workspace,
3023                project_transaction,
3024                format!("Edits from {}", title),
3025                cx,
3026            )
3027            .await
3028        })
3029        .detach_and_log_err(cx);
3030    }
3031
3032    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
3033        self.context
3034            .update(cx, |context, cx| context.save(None, self.fs.clone(), cx));
3035    }
3036
3037    fn title(&self, cx: &AppContext) -> String {
3038        self.context
3039            .read(cx)
3040            .summary
3041            .as_ref()
3042            .map(|summary| summary.text.clone())
3043            .unwrap_or_else(|| "New Context".into())
3044    }
3045
3046    fn render_send_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3047        let focus_handle = self.focus_handle(cx).clone();
3048        ButtonLike::new("send_button")
3049            .style(ButtonStyle::Filled)
3050            .layer(ElevationIndex::ModalSurface)
3051            .children(
3052                KeyBinding::for_action_in(&Assist, &focus_handle, cx)
3053                    .map(|binding| binding.into_any_element()),
3054            )
3055            .child(Label::new("Send"))
3056            .on_click(move |_event, cx| {
3057                focus_handle.dispatch_action(&Assist, cx);
3058            })
3059    }
3060}
3061
3062impl EventEmitter<ContextEditorEvent> for ContextEditor {}
3063impl EventEmitter<SearchEvent> for ContextEditor {}
3064
3065impl Render for ContextEditor {
3066    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3067        div()
3068            .key_context("ContextEditor")
3069            .capture_action(cx.listener(ContextEditor::cancel_last_assist))
3070            .capture_action(cx.listener(ContextEditor::save))
3071            .capture_action(cx.listener(ContextEditor::copy))
3072            .capture_action(cx.listener(ContextEditor::cycle_message_role))
3073            .capture_action(cx.listener(ContextEditor::confirm_command))
3074            .on_action(cx.listener(ContextEditor::assist))
3075            .on_action(cx.listener(ContextEditor::split))
3076            .on_action(cx.listener(ContextEditor::apply_edit))
3077            .size_full()
3078            .v_flex()
3079            .child(
3080                div()
3081                    .flex_grow()
3082                    .bg(cx.theme().colors().editor_background)
3083                    .child(self.editor.clone())
3084                    .child(
3085                        h_flex()
3086                            .w_full()
3087                            .absolute()
3088                            .bottom_0()
3089                            .p_4()
3090                            .justify_end()
3091                            .child(self.render_send_button(cx)),
3092                    ),
3093            )
3094    }
3095}
3096
3097impl FocusableView for ContextEditor {
3098    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3099        self.editor.focus_handle(cx)
3100    }
3101}
3102
3103impl Item for ContextEditor {
3104    type Event = ContextEditorEvent;
3105
3106    fn tab_content(
3107        &self,
3108        params: workspace::item::TabContentParams,
3109        cx: &WindowContext,
3110    ) -> AnyElement {
3111        let color = if params.selected {
3112            Color::Default
3113        } else {
3114            Color::Muted
3115        };
3116        Label::new(util::truncate_and_trailoff(
3117            &self.title(cx),
3118            Self::MAX_TAB_TITLE_LEN,
3119        ))
3120        .color(color)
3121        .into_any_element()
3122    }
3123
3124    fn to_item_events(event: &Self::Event, mut f: impl FnMut(workspace::item::ItemEvent)) {
3125        match event {
3126            ContextEditorEvent::Edited => {
3127                f(workspace::item::ItemEvent::Edit);
3128                f(workspace::item::ItemEvent::UpdateBreadcrumbs);
3129            }
3130            ContextEditorEvent::TabContentChanged => {
3131                f(workspace::item::ItemEvent::UpdateTab);
3132            }
3133        }
3134    }
3135
3136    fn tab_tooltip_text(&self, cx: &AppContext) -> Option<SharedString> {
3137        Some(self.title(cx).into())
3138    }
3139
3140    fn as_searchable(&self, handle: &View<Self>) -> Option<Box<dyn SearchableItemHandle>> {
3141        Some(Box::new(handle.clone()))
3142    }
3143
3144    fn breadcrumbs(
3145        &self,
3146        theme: &theme::Theme,
3147        cx: &AppContext,
3148    ) -> Option<Vec<workspace::item::BreadcrumbText>> {
3149        let editor = self.editor.read(cx);
3150        let cursor = editor.selections.newest_anchor().head();
3151        let multibuffer = &editor.buffer().read(cx);
3152        let (_, symbols) = multibuffer.symbols_containing(cursor, Some(&theme.syntax()), cx)?;
3153
3154        let settings = ThemeSettings::get_global(cx);
3155
3156        let mut breadcrumbs = Vec::new();
3157
3158        let title = self.title(cx);
3159        if title.chars().count() > Self::MAX_TAB_TITLE_LEN {
3160            breadcrumbs.push(BreadcrumbText {
3161                text: title,
3162                highlights: None,
3163                font: Some(settings.buffer_font.clone()),
3164            });
3165        }
3166
3167        breadcrumbs.extend(symbols.into_iter().map(|symbol| BreadcrumbText {
3168            text: symbol.text,
3169            highlights: Some(symbol.highlight_ranges),
3170            font: Some(settings.buffer_font.clone()),
3171        }));
3172        Some(breadcrumbs)
3173    }
3174
3175    fn breadcrumb_location(&self) -> ToolbarItemLocation {
3176        ToolbarItemLocation::PrimaryLeft
3177    }
3178
3179    fn set_nav_history(&mut self, nav_history: pane::ItemNavHistory, cx: &mut ViewContext<Self>) {
3180        self.editor.update(cx, |editor, cx| {
3181            Item::set_nav_history(editor, nav_history, cx)
3182        })
3183    }
3184
3185    fn navigate(&mut self, data: Box<dyn std::any::Any>, cx: &mut ViewContext<Self>) -> bool {
3186        self.editor
3187            .update(cx, |editor, cx| Item::navigate(editor, data, cx))
3188    }
3189
3190    fn deactivated(&mut self, cx: &mut ViewContext<Self>) {
3191        self.editor
3192            .update(cx, |editor, cx| Item::deactivated(editor, cx))
3193    }
3194}
3195
3196impl SearchableItem for ContextEditor {
3197    type Match = <Editor as SearchableItem>::Match;
3198
3199    fn clear_matches(&mut self, cx: &mut ViewContext<Self>) {
3200        self.editor.update(cx, |editor, cx| {
3201            editor.clear_matches(cx);
3202        });
3203    }
3204
3205    fn update_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
3206        self.editor
3207            .update(cx, |editor, cx| editor.update_matches(matches, cx));
3208    }
3209
3210    fn query_suggestion(&mut self, cx: &mut ViewContext<Self>) -> String {
3211        self.editor
3212            .update(cx, |editor, cx| editor.query_suggestion(cx))
3213    }
3214
3215    fn activate_match(
3216        &mut self,
3217        index: usize,
3218        matches: &[Self::Match],
3219        cx: &mut ViewContext<Self>,
3220    ) {
3221        self.editor.update(cx, |editor, cx| {
3222            editor.activate_match(index, matches, cx);
3223        });
3224    }
3225
3226    fn select_matches(&mut self, matches: &[Self::Match], cx: &mut ViewContext<Self>) {
3227        self.editor
3228            .update(cx, |editor, cx| editor.select_matches(matches, cx));
3229    }
3230
3231    fn replace(
3232        &mut self,
3233        identifier: &Self::Match,
3234        query: &project::search::SearchQuery,
3235        cx: &mut ViewContext<Self>,
3236    ) {
3237        self.editor
3238            .update(cx, |editor, cx| editor.replace(identifier, query, cx));
3239    }
3240
3241    fn find_matches(
3242        &mut self,
3243        query: Arc<project::search::SearchQuery>,
3244        cx: &mut ViewContext<Self>,
3245    ) -> Task<Vec<Self::Match>> {
3246        self.editor
3247            .update(cx, |editor, cx| editor.find_matches(query, cx))
3248    }
3249
3250    fn active_match_index(
3251        &mut self,
3252        matches: &[Self::Match],
3253        cx: &mut ViewContext<Self>,
3254    ) -> Option<usize> {
3255        self.editor
3256            .update(cx, |editor, cx| editor.active_match_index(matches, cx))
3257    }
3258}
3259
3260pub struct ContextEditorToolbarItem {
3261    fs: Arc<dyn Fs>,
3262    workspace: WeakView<Workspace>,
3263    active_context_editor: Option<WeakView<ContextEditor>>,
3264    model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
3265}
3266
3267impl ContextEditorToolbarItem {
3268    pub fn new(
3269        workspace: &Workspace,
3270        model_selector_menu_handle: PopoverMenuHandle<ContextMenu>,
3271    ) -> Self {
3272        Self {
3273            fs: workspace.app_state().fs.clone(),
3274            workspace: workspace.weak_handle(),
3275            active_context_editor: None,
3276            model_selector_menu_handle,
3277        }
3278    }
3279
3280    fn render_inject_context_menu(&self, cx: &mut ViewContext<Self>) -> impl Element {
3281        let commands = SlashCommandRegistry::global(cx);
3282        let active_editor_focus_handle = self.workspace.upgrade().and_then(|workspace| {
3283            Some(
3284                workspace
3285                    .read(cx)
3286                    .active_item_as::<Editor>(cx)?
3287                    .focus_handle(cx),
3288            )
3289        });
3290        let active_context_editor = self.active_context_editor.clone();
3291
3292        PopoverMenu::new("inject-context-menu")
3293            .trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
3294                Tooltip::with_meta("Insert Context", None, "Type / to insert via keyboard", cx)
3295            }))
3296            .menu(move |cx| {
3297                let active_context_editor = active_context_editor.clone()?;
3298                ContextMenu::build(cx, |mut menu, _cx| {
3299                    for command_name in commands.featured_command_names() {
3300                        if let Some(command) = commands.command(&command_name) {
3301                            let menu_text = SharedString::from(Arc::from(command.menu_text()));
3302                            menu = menu.custom_entry(
3303                                {
3304                                    let command_name = command_name.clone();
3305                                    move |_cx| {
3306                                        h_flex()
3307                                            .gap_4()
3308                                            .w_full()
3309                                            .justify_between()
3310                                            .child(Label::new(menu_text.clone()))
3311                                            .child(
3312                                                Label::new(format!("/{command_name}"))
3313                                                    .color(Color::Muted),
3314                                            )
3315                                            .into_any()
3316                                    }
3317                                },
3318                                {
3319                                    let active_context_editor = active_context_editor.clone();
3320                                    move |cx| {
3321                                        active_context_editor
3322                                            .update(cx, |context_editor, cx| {
3323                                                context_editor.insert_command(&command_name, cx)
3324                                            })
3325                                            .ok();
3326                                    }
3327                                },
3328                            )
3329                        }
3330                    }
3331
3332                    if let Some(active_editor_focus_handle) = active_editor_focus_handle.clone() {
3333                        menu = menu
3334                            .context(active_editor_focus_handle)
3335                            .action("Quote Selection", Box::new(QuoteSelection));
3336                    }
3337
3338                    menu
3339                })
3340                .into()
3341            })
3342    }
3343
3344    fn render_remaining_tokens(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
3345        let model = CompletionProvider::global(cx).model();
3346        let context = &self
3347            .active_context_editor
3348            .as_ref()?
3349            .upgrade()?
3350            .read(cx)
3351            .context;
3352        let token_count = context.read(cx).token_count()?;
3353        let max_token_count = model.max_token_count();
3354
3355        let remaining_tokens = max_token_count as isize - token_count as isize;
3356        let token_count_color = if remaining_tokens <= 0 {
3357            Color::Error
3358        } else if token_count as f32 / max_token_count as f32 >= 0.8 {
3359            Color::Warning
3360        } else {
3361            Color::Muted
3362        };
3363
3364        Some(
3365            h_flex()
3366                .gap_0p5()
3367                .child(
3368                    Label::new(humanize_token_count(token_count))
3369                        .size(LabelSize::Small)
3370                        .color(token_count_color),
3371                )
3372                .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
3373                .child(
3374                    Label::new(humanize_token_count(max_token_count))
3375                        .size(LabelSize::Small)
3376                        .color(Color::Muted),
3377                ),
3378        )
3379    }
3380}
3381
3382impl Render for ContextEditorToolbarItem {
3383    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3384        h_flex()
3385            .gap_2()
3386            .child(ModelSelector::new(
3387                self.model_selector_menu_handle.clone(),
3388                self.fs.clone(),
3389            ))
3390            .children(self.render_remaining_tokens(cx))
3391            .child(self.render_inject_context_menu(cx))
3392    }
3393}
3394
3395impl ToolbarItemView for ContextEditorToolbarItem {
3396    fn set_active_pane_item(
3397        &mut self,
3398        active_pane_item: Option<&dyn ItemHandle>,
3399        cx: &mut ViewContext<Self>,
3400    ) -> ToolbarItemLocation {
3401        self.active_context_editor = active_pane_item
3402            .and_then(|item| item.act_as::<ContextEditor>(cx))
3403            .map(|editor| editor.downgrade());
3404        cx.notify();
3405        if self.active_context_editor.is_none() {
3406            ToolbarItemLocation::Hidden
3407        } else {
3408            ToolbarItemLocation::PrimaryRight
3409        }
3410    }
3411
3412    fn pane_focus_update(&mut self, _pane_focused: bool, cx: &mut ViewContext<Self>) {
3413        cx.notify();
3414    }
3415}
3416
3417impl EventEmitter<ToolbarItemEvent> for ContextEditorToolbarItem {}
3418
3419pub struct ContextHistory {
3420    picker: View<Picker<SavedContextPickerDelegate>>,
3421    _subscriptions: Vec<Subscription>,
3422    assistant_panel: WeakView<AssistantPanel>,
3423}
3424
3425impl ContextHistory {
3426    fn new(
3427        context_store: Model<ContextStore>,
3428        assistant_panel: WeakView<AssistantPanel>,
3429        cx: &mut ViewContext<Self>,
3430    ) -> Self {
3431        let picker = cx.new_view(|cx| {
3432            Picker::uniform_list(SavedContextPickerDelegate::new(context_store.clone()), cx)
3433                .modal(false)
3434                .max_height(None)
3435        });
3436
3437        let _subscriptions = vec![
3438            cx.observe(&context_store, |this, _, cx| {
3439                this.picker.update(cx, |picker, cx| picker.refresh(cx));
3440            }),
3441            cx.subscribe(&picker, Self::handle_picker_event),
3442        ];
3443
3444        Self {
3445            picker,
3446            _subscriptions,
3447            assistant_panel,
3448        }
3449    }
3450
3451    fn handle_picker_event(
3452        &mut self,
3453        _: View<Picker<SavedContextPickerDelegate>>,
3454        event: &SavedContextPickerEvent,
3455        cx: &mut ViewContext<Self>,
3456    ) {
3457        let SavedContextPickerEvent::Confirmed { path } = event;
3458        self.assistant_panel
3459            .update(cx, |assistant_panel, cx| {
3460                assistant_panel
3461                    .open_context(path.clone(), cx)
3462                    .detach_and_log_err(cx);
3463            })
3464            .ok();
3465    }
3466}
3467
3468impl Render for ContextHistory {
3469    fn render(&mut self, _: &mut ViewContext<Self>) -> impl IntoElement {
3470        div().size_full().child(self.picker.clone())
3471    }
3472}
3473
3474impl FocusableView for ContextHistory {
3475    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3476        self.picker.focus_handle(cx)
3477    }
3478}
3479
3480impl EventEmitter<()> for ContextHistory {}
3481
3482impl Item for ContextHistory {
3483    type Event = ();
3484
3485    fn tab_content(
3486        &self,
3487        params: workspace::item::TabContentParams,
3488        _: &WindowContext,
3489    ) -> AnyElement {
3490        let color = if params.selected {
3491            Color::Default
3492        } else {
3493            Color::Muted
3494        };
3495        Label::new("History").color(color).into_any_element()
3496    }
3497}
3498
3499#[derive(Clone, Debug)]
3500struct MessageAnchor {
3501    id: MessageId,
3502    start: language::Anchor,
3503}
3504
3505#[derive(Clone, Debug)]
3506pub struct Message {
3507    offset_range: Range<usize>,
3508    index_range: Range<usize>,
3509    id: MessageId,
3510    anchor: language::Anchor,
3511    role: Role,
3512    status: MessageStatus,
3513}
3514
3515impl Message {
3516    fn to_request_message(&self, buffer: &Buffer) -> LanguageModelRequestMessage {
3517        LanguageModelRequestMessage {
3518            role: self.role,
3519            content: buffer.text_for_range(self.offset_range.clone()).collect(),
3520        }
3521    }
3522}
3523
3524type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
3525
3526fn render_slash_command_output_toggle(
3527    row: MultiBufferRow,
3528    is_folded: bool,
3529    fold: ToggleFold,
3530    _cx: &mut WindowContext,
3531) -> AnyElement {
3532    Disclosure::new(("slash-command-output-fold-indicator", row.0), !is_folded)
3533        .selected(is_folded)
3534        .on_click(move |_e, cx| fold(!is_folded, cx))
3535        .into_any_element()
3536}
3537
3538fn render_pending_slash_command_gutter_decoration(
3539    row: MultiBufferRow,
3540    status: &PendingSlashCommandStatus,
3541    confirm_command: Arc<dyn Fn(&mut WindowContext)>,
3542) -> AnyElement {
3543    let mut icon = IconButton::new(
3544        ("slash-command-gutter-decoration", row.0),
3545        ui::IconName::TriangleRight,
3546    )
3547    .on_click(move |_e, cx| confirm_command(cx))
3548    .icon_size(ui::IconSize::Small)
3549    .size(ui::ButtonSize::None);
3550
3551    match status {
3552        PendingSlashCommandStatus::Idle => {
3553            icon = icon.icon_color(Color::Muted);
3554        }
3555        PendingSlashCommandStatus::Running { .. } => {
3556            icon = icon.selected(true);
3557        }
3558        PendingSlashCommandStatus::Error(_) => icon = icon.icon_color(Color::Error),
3559    }
3560
3561    icon.into_any_element()
3562}
3563
3564fn render_docs_slash_command_trailer(
3565    row: MultiBufferRow,
3566    command: PendingSlashCommand,
3567    cx: &mut WindowContext,
3568) -> AnyElement {
3569    let Some(argument) = command.argument else {
3570        return Empty.into_any();
3571    };
3572
3573    let args = DocsSlashCommandArgs::parse(&argument);
3574
3575    let Some(store) = args
3576        .provider()
3577        .and_then(|provider| IndexedDocsStore::try_global(provider, cx).ok())
3578    else {
3579        return Empty.into_any();
3580    };
3581
3582    let Some(package) = args.package() else {
3583        return Empty.into_any();
3584    };
3585
3586    if !store.is_indexing(&package) {
3587        return Empty.into_any();
3588    }
3589
3590    div()
3591        .id(("crates-being-indexed", row.0))
3592        .child(Icon::new(IconName::ArrowCircle).with_animation(
3593            "arrow-circle",
3594            Animation::new(Duration::from_secs(4)).repeat(),
3595            |icon, delta| icon.transform(Transformation::rotate(percentage(delta))),
3596        ))
3597        .tooltip(move |cx| Tooltip::text(format!("Indexing {package}"), cx))
3598        .into_any_element()
3599}
3600
3601fn make_lsp_adapter_delegate(
3602    project: &Model<Project>,
3603    cx: &mut AppContext,
3604) -> Result<Arc<dyn LspAdapterDelegate>> {
3605    project.update(cx, |project, cx| {
3606        // TODO: Find the right worktree.
3607        let worktree = project
3608            .worktrees()
3609            .next()
3610            .ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
3611        Ok(ProjectLspAdapterDelegate::new(project, &worktree, cx) as Arc<dyn LspAdapterDelegate>)
3612    })
3613}
3614
3615fn slash_command_error_block_renderer(message: String) -> RenderBlock {
3616    Box::new(move |_| {
3617        div()
3618            .pl_6()
3619            .child(
3620                Label::new(format!("error: {}", message))
3621                    .single_line()
3622                    .color(Color::Error),
3623            )
3624            .into_any()
3625    })
3626}
3627
3628#[cfg(test)]
3629mod tests {
3630    use super::*;
3631    use crate::{
3632        slash_command::{active_command, file_command},
3633        FakeCompletionProvider, MessageId,
3634    };
3635    use fs::FakeFs;
3636    use gpui::{AppContext, TestAppContext};
3637    use rope::Rope;
3638    use serde_json::json;
3639    use settings::SettingsStore;
3640    use std::{cell::RefCell, path::Path, rc::Rc};
3641    use unindent::Unindent;
3642    use util::test::marked_text_ranges;
3643
3644    #[gpui::test]
3645    fn test_inserting_and_removing_messages(cx: &mut AppContext) {
3646        let settings_store = SettingsStore::test(cx);
3647        FakeCompletionProvider::setup_test(cx);
3648        cx.set_global(settings_store);
3649        init(cx);
3650        let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
3651
3652        let context = cx.new_model(|cx| Context::new(registry, Default::default(), None, cx));
3653        let buffer = context.read(cx).buffer.clone();
3654
3655        let message_1 = context.read(cx).message_anchors[0].clone();
3656        assert_eq!(
3657            messages(&context, cx),
3658            vec![(message_1.id, Role::User, 0..0)]
3659        );
3660
3661        let message_2 = context.update(cx, |context, cx| {
3662            context
3663                .insert_message_after(message_1.id, Role::Assistant, MessageStatus::Done, cx)
3664                .unwrap()
3665        });
3666        assert_eq!(
3667            messages(&context, cx),
3668            vec![
3669                (message_1.id, Role::User, 0..1),
3670                (message_2.id, Role::Assistant, 1..1)
3671            ]
3672        );
3673
3674        buffer.update(cx, |buffer, cx| {
3675            buffer.edit([(0..0, "1"), (1..1, "2")], None, cx)
3676        });
3677        assert_eq!(
3678            messages(&context, cx),
3679            vec![
3680                (message_1.id, Role::User, 0..2),
3681                (message_2.id, Role::Assistant, 2..3)
3682            ]
3683        );
3684
3685        let message_3 = context.update(cx, |context, cx| {
3686            context
3687                .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
3688                .unwrap()
3689        });
3690        assert_eq!(
3691            messages(&context, cx),
3692            vec![
3693                (message_1.id, Role::User, 0..2),
3694                (message_2.id, Role::Assistant, 2..4),
3695                (message_3.id, Role::User, 4..4)
3696            ]
3697        );
3698
3699        let message_4 = context.update(cx, |context, cx| {
3700            context
3701                .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
3702                .unwrap()
3703        });
3704        assert_eq!(
3705            messages(&context, cx),
3706            vec![
3707                (message_1.id, Role::User, 0..2),
3708                (message_2.id, Role::Assistant, 2..4),
3709                (message_4.id, Role::User, 4..5),
3710                (message_3.id, Role::User, 5..5),
3711            ]
3712        );
3713
3714        buffer.update(cx, |buffer, cx| {
3715            buffer.edit([(4..4, "C"), (5..5, "D")], None, cx)
3716        });
3717        assert_eq!(
3718            messages(&context, cx),
3719            vec![
3720                (message_1.id, Role::User, 0..2),
3721                (message_2.id, Role::Assistant, 2..4),
3722                (message_4.id, Role::User, 4..6),
3723                (message_3.id, Role::User, 6..7),
3724            ]
3725        );
3726
3727        // Deleting across message boundaries merges the messages.
3728        buffer.update(cx, |buffer, cx| buffer.edit([(1..4, "")], None, cx));
3729        assert_eq!(
3730            messages(&context, cx),
3731            vec![
3732                (message_1.id, Role::User, 0..3),
3733                (message_3.id, Role::User, 3..4),
3734            ]
3735        );
3736
3737        // Undoing the deletion should also undo the merge.
3738        buffer.update(cx, |buffer, cx| buffer.undo(cx));
3739        assert_eq!(
3740            messages(&context, cx),
3741            vec![
3742                (message_1.id, Role::User, 0..2),
3743                (message_2.id, Role::Assistant, 2..4),
3744                (message_4.id, Role::User, 4..6),
3745                (message_3.id, Role::User, 6..7),
3746            ]
3747        );
3748
3749        // Redoing the deletion should also redo the merge.
3750        buffer.update(cx, |buffer, cx| buffer.redo(cx));
3751        assert_eq!(
3752            messages(&context, cx),
3753            vec![
3754                (message_1.id, Role::User, 0..3),
3755                (message_3.id, Role::User, 3..4),
3756            ]
3757        );
3758
3759        // Ensure we can still insert after a merged message.
3760        let message_5 = context.update(cx, |context, cx| {
3761            context
3762                .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
3763                .unwrap()
3764        });
3765        assert_eq!(
3766            messages(&context, cx),
3767            vec![
3768                (message_1.id, Role::User, 0..3),
3769                (message_5.id, Role::System, 3..4),
3770                (message_3.id, Role::User, 4..5)
3771            ]
3772        );
3773    }
3774
3775    #[gpui::test]
3776    fn test_message_splitting(cx: &mut AppContext) {
3777        let settings_store = SettingsStore::test(cx);
3778        cx.set_global(settings_store);
3779        FakeCompletionProvider::setup_test(cx);
3780        init(cx);
3781        let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
3782
3783        let context = cx.new_model(|cx| Context::new(registry, Default::default(), None, cx));
3784        let buffer = context.read(cx).buffer.clone();
3785
3786        let message_1 = context.read(cx).message_anchors[0].clone();
3787        assert_eq!(
3788            messages(&context, cx),
3789            vec![(message_1.id, Role::User, 0..0)]
3790        );
3791
3792        buffer.update(cx, |buffer, cx| {
3793            buffer.edit([(0..0, "aaa\nbbb\nccc\nddd\n")], None, cx)
3794        });
3795
3796        let (_, message_2) = context.update(cx, |context, cx| context.split_message(3..3, cx));
3797        let message_2 = message_2.unwrap();
3798
3799        // We recycle newlines in the middle of a split message
3800        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\nddd\n");
3801        assert_eq!(
3802            messages(&context, cx),
3803            vec![
3804                (message_1.id, Role::User, 0..4),
3805                (message_2.id, Role::User, 4..16),
3806            ]
3807        );
3808
3809        let (_, message_3) = context.update(cx, |context, cx| context.split_message(3..3, cx));
3810        let message_3 = message_3.unwrap();
3811
3812        // We don't recycle newlines at the end of a split message
3813        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
3814        assert_eq!(
3815            messages(&context, cx),
3816            vec![
3817                (message_1.id, Role::User, 0..4),
3818                (message_3.id, Role::User, 4..5),
3819                (message_2.id, Role::User, 5..17),
3820            ]
3821        );
3822
3823        let (_, message_4) = context.update(cx, |context, cx| context.split_message(9..9, cx));
3824        let message_4 = message_4.unwrap();
3825        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
3826        assert_eq!(
3827            messages(&context, cx),
3828            vec![
3829                (message_1.id, Role::User, 0..4),
3830                (message_3.id, Role::User, 4..5),
3831                (message_2.id, Role::User, 5..9),
3832                (message_4.id, Role::User, 9..17),
3833            ]
3834        );
3835
3836        let (_, message_5) = context.update(cx, |context, cx| context.split_message(9..9, cx));
3837        let message_5 = message_5.unwrap();
3838        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\nddd\n");
3839        assert_eq!(
3840            messages(&context, cx),
3841            vec![
3842                (message_1.id, Role::User, 0..4),
3843                (message_3.id, Role::User, 4..5),
3844                (message_2.id, Role::User, 5..9),
3845                (message_4.id, Role::User, 9..10),
3846                (message_5.id, Role::User, 10..18),
3847            ]
3848        );
3849
3850        let (message_6, message_7) =
3851            context.update(cx, |context, cx| context.split_message(14..16, cx));
3852        let message_6 = message_6.unwrap();
3853        let message_7 = message_7.unwrap();
3854        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\ndd\nd\n");
3855        assert_eq!(
3856            messages(&context, cx),
3857            vec![
3858                (message_1.id, Role::User, 0..4),
3859                (message_3.id, Role::User, 4..5),
3860                (message_2.id, Role::User, 5..9),
3861                (message_4.id, Role::User, 9..10),
3862                (message_5.id, Role::User, 10..14),
3863                (message_6.id, Role::User, 14..17),
3864                (message_7.id, Role::User, 17..19),
3865            ]
3866        );
3867    }
3868
3869    #[gpui::test]
3870    fn test_messages_for_offsets(cx: &mut AppContext) {
3871        let settings_store = SettingsStore::test(cx);
3872        FakeCompletionProvider::setup_test(cx);
3873        cx.set_global(settings_store);
3874        init(cx);
3875        let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
3876        let context = cx.new_model(|cx| Context::new(registry, Default::default(), None, cx));
3877        let buffer = context.read(cx).buffer.clone();
3878
3879        let message_1 = context.read(cx).message_anchors[0].clone();
3880        assert_eq!(
3881            messages(&context, cx),
3882            vec![(message_1.id, Role::User, 0..0)]
3883        );
3884
3885        buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "aaa")], None, cx));
3886        let message_2 = context
3887            .update(cx, |context, cx| {
3888                context.insert_message_after(message_1.id, Role::User, MessageStatus::Done, cx)
3889            })
3890            .unwrap();
3891        buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "bbb")], None, cx));
3892
3893        let message_3 = context
3894            .update(cx, |context, cx| {
3895                context.insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
3896            })
3897            .unwrap();
3898        buffer.update(cx, |buffer, cx| buffer.edit([(8..8, "ccc")], None, cx));
3899
3900        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc");
3901        assert_eq!(
3902            messages(&context, cx),
3903            vec![
3904                (message_1.id, Role::User, 0..4),
3905                (message_2.id, Role::User, 4..8),
3906                (message_3.id, Role::User, 8..11)
3907            ]
3908        );
3909
3910        assert_eq!(
3911            message_ids_for_offsets(&context, &[0, 4, 9], cx),
3912            [message_1.id, message_2.id, message_3.id]
3913        );
3914        assert_eq!(
3915            message_ids_for_offsets(&context, &[0, 1, 11], cx),
3916            [message_1.id, message_3.id]
3917        );
3918
3919        let message_4 = context
3920            .update(cx, |context, cx| {
3921                context.insert_message_after(message_3.id, Role::User, MessageStatus::Done, cx)
3922            })
3923            .unwrap();
3924        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\n");
3925        assert_eq!(
3926            messages(&context, cx),
3927            vec![
3928                (message_1.id, Role::User, 0..4),
3929                (message_2.id, Role::User, 4..8),
3930                (message_3.id, Role::User, 8..12),
3931                (message_4.id, Role::User, 12..12)
3932            ]
3933        );
3934        assert_eq!(
3935            message_ids_for_offsets(&context, &[0, 4, 8, 12], cx),
3936            [message_1.id, message_2.id, message_3.id, message_4.id]
3937        );
3938
3939        fn message_ids_for_offsets(
3940            context: &Model<Context>,
3941            offsets: &[usize],
3942            cx: &AppContext,
3943        ) -> Vec<MessageId> {
3944            context
3945                .read(cx)
3946                .messages_for_offsets(offsets.iter().copied(), cx)
3947                .into_iter()
3948                .map(|message| message.id)
3949                .collect()
3950        }
3951    }
3952
3953    #[gpui::test]
3954    async fn test_slash_commands(cx: &mut TestAppContext) {
3955        let settings_store = cx.update(SettingsStore::test);
3956        cx.set_global(settings_store);
3957        cx.update(|cx| FakeCompletionProvider::setup_test(cx));
3958
3959        cx.update(Project::init_settings);
3960        cx.update(init);
3961        let fs = FakeFs::new(cx.background_executor.clone());
3962
3963        fs.insert_tree(
3964            "/test",
3965            json!({
3966                "src": {
3967                    "lib.rs": "fn one() -> usize { 1 }",
3968                    "main.rs": "
3969                        use crate::one;
3970                        fn main() { one(); }
3971                    ".unindent(),
3972                }
3973            }),
3974        )
3975        .await;
3976
3977        let slash_command_registry = SlashCommandRegistry::new();
3978        slash_command_registry.register_command(file_command::FileSlashCommand, false);
3979        slash_command_registry.register_command(active_command::ActiveSlashCommand, false);
3980
3981        let registry = Arc::new(LanguageRegistry::test(cx.executor()));
3982        let context =
3983            cx.new_model(|cx| Context::new(registry.clone(), slash_command_registry, None, cx));
3984
3985        let output_ranges = Rc::new(RefCell::new(HashSet::default()));
3986        context.update(cx, |_, cx| {
3987            cx.subscribe(&context, {
3988                let ranges = output_ranges.clone();
3989                move |_, _, event, _| match event {
3990                    ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
3991                        for range in removed {
3992                            ranges.borrow_mut().remove(range);
3993                        }
3994                        for command in updated {
3995                            ranges.borrow_mut().insert(command.source_range.clone());
3996                        }
3997                    }
3998                    _ => {}
3999                }
4000            })
4001            .detach();
4002        });
4003
4004        let buffer = context.read_with(cx, |context, _| context.buffer.clone());
4005
4006        // Insert a slash command
4007        buffer.update(cx, |buffer, cx| {
4008            buffer.edit([(0..0, "/file src/lib.rs")], None, cx);
4009        });
4010        assert_text_and_output_ranges(
4011            &buffer,
4012            &output_ranges.borrow(),
4013            "
4014            «/file src/lib.rs»
4015            "
4016            .unindent()
4017            .trim_end(),
4018            cx,
4019        );
4020
4021        // Edit the argument of the slash command.
4022        buffer.update(cx, |buffer, cx| {
4023            let edit_offset = buffer.text().find("lib.rs").unwrap();
4024            buffer.edit([(edit_offset..edit_offset + "lib".len(), "main")], None, cx);
4025        });
4026        assert_text_and_output_ranges(
4027            &buffer,
4028            &output_ranges.borrow(),
4029            "
4030            «/file src/main.rs»
4031            "
4032            .unindent()
4033            .trim_end(),
4034            cx,
4035        );
4036
4037        // Edit the name of the slash command, using one that doesn't exist.
4038        buffer.update(cx, |buffer, cx| {
4039            let edit_offset = buffer.text().find("/file").unwrap();
4040            buffer.edit(
4041                [(edit_offset..edit_offset + "/file".len(), "/unknown")],
4042                None,
4043                cx,
4044            );
4045        });
4046        assert_text_and_output_ranges(
4047            &buffer,
4048            &output_ranges.borrow(),
4049            "
4050            /unknown src/main.rs
4051            "
4052            .unindent()
4053            .trim_end(),
4054            cx,
4055        );
4056
4057        #[track_caller]
4058        fn assert_text_and_output_ranges(
4059            buffer: &Model<Buffer>,
4060            ranges: &HashSet<Range<language::Anchor>>,
4061            expected_marked_text: &str,
4062            cx: &mut TestAppContext,
4063        ) {
4064            let (expected_text, expected_ranges) = marked_text_ranges(expected_marked_text, false);
4065            let (actual_text, actual_ranges) = buffer.update(cx, |buffer, _| {
4066                let mut ranges = ranges
4067                    .iter()
4068                    .map(|range| range.to_offset(buffer))
4069                    .collect::<Vec<_>>();
4070                ranges.sort_by_key(|a| a.start);
4071                (buffer.text(), ranges)
4072            });
4073
4074            assert_eq!(actual_text, expected_text);
4075            assert_eq!(actual_ranges, expected_ranges);
4076        }
4077    }
4078
4079    #[test]
4080    fn test_parse_next_edit_suggestion() {
4081        let text = "
4082            some output:
4083
4084            ```edit src/foo.rs
4085                let a = 1;
4086                let b = 2;
4087            ---
4088                let w = 1;
4089                let x = 2;
4090                let y = 3;
4091                let z = 4;
4092            ```
4093
4094            some more output:
4095
4096            ```edit src/foo.rs
4097                let c = 1;
4098            ---
4099            ```
4100
4101            and the conclusion.
4102        "
4103        .unindent();
4104
4105        let rope = Rope::from(text.as_str());
4106        let mut lines = rope.chunks().lines();
4107        let mut suggestions = vec![];
4108        while let Some(suggestion) = parse_next_edit_suggestion(&mut lines) {
4109            suggestions.push((
4110                suggestion.path.clone(),
4111                text[suggestion.old_text_range].to_string(),
4112                text[suggestion.new_text_range].to_string(),
4113            ));
4114        }
4115
4116        assert_eq!(
4117            suggestions,
4118            vec![
4119                (
4120                    Path::new("src/foo.rs").into(),
4121                    [
4122                        "    let a = 1;", //
4123                        "    let b = 2;",
4124                        "",
4125                    ]
4126                    .join("\n"),
4127                    [
4128                        "    let w = 1;",
4129                        "    let x = 2;",
4130                        "    let y = 3;",
4131                        "    let z = 4;",
4132                        "",
4133                    ]
4134                    .join("\n"),
4135                ),
4136                (
4137                    Path::new("src/foo.rs").into(),
4138                    [
4139                        "    let c = 1;", //
4140                        "",
4141                    ]
4142                    .join("\n"),
4143                    String::new(),
4144                )
4145            ]
4146        );
4147    }
4148
4149    #[gpui::test]
4150    async fn test_serialization(cx: &mut TestAppContext) {
4151        let settings_store = cx.update(SettingsStore::test);
4152        cx.set_global(settings_store);
4153        cx.update(FakeCompletionProvider::setup_test);
4154        cx.update(init);
4155        let registry = Arc::new(LanguageRegistry::test(cx.executor()));
4156        let context =
4157            cx.new_model(|cx| Context::new(registry.clone(), Default::default(), None, cx));
4158        let buffer = context.read_with(cx, |context, _| context.buffer.clone());
4159        let message_0 = context.read_with(cx, |context, _| context.message_anchors[0].id);
4160        let message_1 = context.update(cx, |context, cx| {
4161            context
4162                .insert_message_after(message_0, Role::Assistant, MessageStatus::Done, cx)
4163                .unwrap()
4164        });
4165        let message_2 = context.update(cx, |context, cx| {
4166            context
4167                .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
4168                .unwrap()
4169        });
4170        buffer.update(cx, |buffer, cx| {
4171            buffer.edit([(0..0, "a"), (1..1, "b\nc")], None, cx);
4172            buffer.finalize_last_transaction();
4173        });
4174        let _message_3 = context.update(cx, |context, cx| {
4175            context
4176                .insert_message_after(message_2.id, Role::System, MessageStatus::Done, cx)
4177                .unwrap()
4178        });
4179        buffer.update(cx, |buffer, cx| buffer.undo(cx));
4180        assert_eq!(buffer.read_with(cx, |buffer, _| buffer.text()), "a\nb\nc\n");
4181        assert_eq!(
4182            cx.read(|cx| messages(&context, cx)),
4183            [
4184                (message_0, Role::User, 0..2),
4185                (message_1.id, Role::Assistant, 2..6),
4186                (message_2.id, Role::System, 6..6),
4187            ]
4188        );
4189
4190        let deserialized_context = Context::deserialize(
4191            context.read_with(cx, |context, cx| context.serialize(cx)),
4192            Default::default(),
4193            registry.clone(),
4194            Default::default(),
4195            None,
4196            &mut cx.to_async(),
4197        )
4198        .await
4199        .unwrap();
4200        let deserialized_buffer =
4201            deserialized_context.read_with(cx, |context, _| context.buffer.clone());
4202        assert_eq!(
4203            deserialized_buffer.read_with(cx, |buffer, _| buffer.text()),
4204            "a\nb\nc\n"
4205        );
4206        assert_eq!(
4207            cx.read(|cx| messages(&deserialized_context, cx)),
4208            [
4209                (message_0, Role::User, 0..2),
4210                (message_1.id, Role::Assistant, 2..6),
4211                (message_2.id, Role::System, 6..6),
4212            ]
4213        );
4214    }
4215
4216    fn messages(context: &Model<Context>, cx: &AppContext) -> Vec<(MessageId, Role, Range<usize>)> {
4217        context
4218            .read(cx)
4219            .messages(cx)
4220            .map(|message| (message.id, message.role, message.offset_range))
4221            .collect()
4222    }
4223}