assistant_panel.rs

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