assistant_panel.rs

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