assistant_panel.rs

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