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