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