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