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 token_count(&self) -> Option<usize> {
1226        self.token_count
1227    }
1228
1229    pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
1230        let request = self.to_completion_request(cx);
1231        self.pending_token_count = cx.spawn(|this, mut cx| {
1232            async move {
1233                cx.background_executor()
1234                    .timer(Duration::from_millis(200))
1235                    .await;
1236
1237                let token_count = cx
1238                    .update(|cx| CompletionProvider::global(cx).count_tokens(request, cx))?
1239                    .await?;
1240
1241                this.update(&mut cx, |this, cx| {
1242                    this.token_count = Some(token_count);
1243                    cx.notify()
1244                })?;
1245                anyhow::Ok(())
1246            }
1247            .log_err()
1248        });
1249    }
1250
1251    fn reparse_slash_commands(&mut self, cx: &mut ModelContext<Self>) {
1252        let buffer = self.buffer.read(cx);
1253        let mut row_ranges = self
1254            .edits_since_last_slash_command_parse
1255            .consume()
1256            .into_iter()
1257            .map(|edit| {
1258                let start_row = buffer.offset_to_point(edit.new.start).row;
1259                let end_row = buffer.offset_to_point(edit.new.end).row + 1;
1260                start_row..end_row
1261            })
1262            .peekable();
1263
1264        let mut removed = Vec::new();
1265        let mut updated = Vec::new();
1266        while let Some(mut row_range) = row_ranges.next() {
1267            while let Some(next_row_range) = row_ranges.peek() {
1268                if row_range.end >= next_row_range.start {
1269                    row_range.end = next_row_range.end;
1270                    row_ranges.next();
1271                } else {
1272                    break;
1273                }
1274            }
1275
1276            let start = buffer.anchor_before(Point::new(row_range.start, 0));
1277            let end = buffer.anchor_after(Point::new(
1278                row_range.end - 1,
1279                buffer.line_len(row_range.end - 1),
1280            ));
1281
1282            let old_range = self.pending_command_indices_for_range(start..end, cx);
1283
1284            let mut new_commands = Vec::new();
1285            let mut lines = buffer.text_for_range(start..end).lines();
1286            let mut offset = lines.offset();
1287            while let Some(line) = lines.next() {
1288                if let Some(command_line) = SlashCommandLine::parse(line) {
1289                    let name = &line[command_line.name.clone()];
1290                    let argument = command_line.argument.as_ref().and_then(|argument| {
1291                        (!argument.is_empty()).then_some(&line[argument.clone()])
1292                    });
1293                    if let Some(command) = self.slash_command_registry.command(name) {
1294                        if !command.requires_argument() || argument.is_some() {
1295                            let start_ix = offset + command_line.name.start - 1;
1296                            let end_ix = offset
1297                                + command_line
1298                                    .argument
1299                                    .map_or(command_line.name.end, |argument| argument.end);
1300                            let source_range =
1301                                buffer.anchor_after(start_ix)..buffer.anchor_after(end_ix);
1302                            let pending_command = PendingSlashCommand {
1303                                name: name.to_string(),
1304                                argument: argument.map(ToString::to_string),
1305                                source_range,
1306                                status: PendingSlashCommandStatus::Idle,
1307                            };
1308                            updated.push(pending_command.clone());
1309                            new_commands.push(pending_command);
1310                        }
1311                    }
1312                }
1313
1314                offset = lines.offset();
1315            }
1316
1317            let removed_commands = self.pending_slash_commands.splice(old_range, new_commands);
1318            removed.extend(removed_commands.map(|command| command.source_range));
1319        }
1320
1321        if !updated.is_empty() || !removed.is_empty() {
1322            cx.emit(ContextEvent::PendingSlashCommandsUpdated { removed, updated });
1323        }
1324    }
1325
1326    fn reparse_edit_suggestions(&mut self, cx: &mut ModelContext<Self>) {
1327        self.pending_edit_suggestion_parse = Some(cx.spawn(|this, mut cx| async move {
1328            cx.background_executor()
1329                .timer(Duration::from_millis(200))
1330                .await;
1331
1332            this.update(&mut cx, |this, cx| {
1333                this.reparse_edit_suggestions_in_range(0..this.buffer.read(cx).len(), cx);
1334            })
1335            .ok();
1336        }));
1337    }
1338
1339    fn reparse_edit_suggestions_in_range(
1340        &mut self,
1341        range: Range<usize>,
1342        cx: &mut ModelContext<Self>,
1343    ) {
1344        self.buffer.update(cx, |buffer, _| {
1345            let range_start = buffer.anchor_before(range.start);
1346            let range_end = buffer.anchor_after(range.end);
1347            let start_ix = self
1348                .edit_suggestions
1349                .binary_search_by(|probe| {
1350                    probe
1351                        .source_range
1352                        .end
1353                        .cmp(&range_start, buffer)
1354                        .then(Ordering::Greater)
1355                })
1356                .unwrap_err();
1357            let end_ix = self
1358                .edit_suggestions
1359                .binary_search_by(|probe| {
1360                    probe
1361                        .source_range
1362                        .start
1363                        .cmp(&range_end, buffer)
1364                        .then(Ordering::Less)
1365                })
1366                .unwrap_err();
1367
1368            let mut new_edit_suggestions = Vec::new();
1369            let mut message_lines = buffer.as_rope().chunks_in_range(range).lines();
1370            while let Some(suggestion) = parse_next_edit_suggestion(&mut message_lines) {
1371                let start_anchor = buffer.anchor_after(suggestion.outer_range.start);
1372                let end_anchor = buffer.anchor_before(suggestion.outer_range.end);
1373                new_edit_suggestions.push(EditSuggestion {
1374                    source_range: start_anchor..end_anchor,
1375                    full_path: suggestion.path,
1376                });
1377            }
1378            self.edit_suggestions
1379                .splice(start_ix..end_ix, new_edit_suggestions);
1380        });
1381        cx.emit(ContextEvent::EditSuggestionsChanged);
1382        cx.notify();
1383    }
1384
1385    fn pending_command_for_position(
1386        &mut self,
1387        position: language::Anchor,
1388        cx: &mut ModelContext<Self>,
1389    ) -> Option<&mut PendingSlashCommand> {
1390        let buffer = self.buffer.read(cx);
1391        match self
1392            .pending_slash_commands
1393            .binary_search_by(|probe| probe.source_range.end.cmp(&position, buffer))
1394        {
1395            Ok(ix) => Some(&mut self.pending_slash_commands[ix]),
1396            Err(ix) => {
1397                let cmd = self.pending_slash_commands.get_mut(ix)?;
1398                if position.cmp(&cmd.source_range.start, buffer).is_ge()
1399                    && position.cmp(&cmd.source_range.end, buffer).is_le()
1400                {
1401                    Some(cmd)
1402                } else {
1403                    None
1404                }
1405            }
1406        }
1407    }
1408
1409    fn pending_commands_for_range(
1410        &self,
1411        range: Range<language::Anchor>,
1412        cx: &AppContext,
1413    ) -> &[PendingSlashCommand] {
1414        let range = self.pending_command_indices_for_range(range, cx);
1415        &self.pending_slash_commands[range]
1416    }
1417
1418    fn pending_command_indices_for_range(
1419        &self,
1420        range: Range<language::Anchor>,
1421        cx: &AppContext,
1422    ) -> Range<usize> {
1423        let buffer = self.buffer.read(cx);
1424        let start_ix = match self
1425            .pending_slash_commands
1426            .binary_search_by(|probe| probe.source_range.end.cmp(&range.start, &buffer))
1427        {
1428            Ok(ix) | Err(ix) => ix,
1429        };
1430        let end_ix = match self
1431            .pending_slash_commands
1432            .binary_search_by(|probe| probe.source_range.start.cmp(&range.end, &buffer))
1433        {
1434            Ok(ix) => ix + 1,
1435            Err(ix) => ix,
1436        };
1437        start_ix..end_ix
1438    }
1439
1440    fn insert_command_output(
1441        &mut self,
1442        command_range: Range<language::Anchor>,
1443        output: Task<Result<SlashCommandOutput>>,
1444        insert_trailing_newline: bool,
1445        cx: &mut ModelContext<Self>,
1446    ) {
1447        self.reparse_slash_commands(cx);
1448
1449        let insert_output_task = cx.spawn(|this, mut cx| {
1450            let command_range = command_range.clone();
1451            async move {
1452                let output = output.await;
1453                this.update(&mut cx, |this, cx| match output {
1454                    Ok(mut output) => {
1455                        if insert_trailing_newline {
1456                            output.text.push('\n');
1457                        }
1458
1459                        let event = this.buffer.update(cx, |buffer, cx| {
1460                            let start = command_range.start.to_offset(buffer);
1461                            let old_end = command_range.end.to_offset(buffer);
1462                            let new_end = start + output.text.len();
1463                            buffer.edit([(start..old_end, output.text)], None, cx);
1464
1465                            let mut sections = output
1466                                .sections
1467                                .into_iter()
1468                                .map(|section| SlashCommandOutputSection {
1469                                    range: buffer.anchor_after(start + section.range.start)
1470                                        ..buffer.anchor_before(start + section.range.end),
1471                                    render_placeholder: section.render_placeholder,
1472                                })
1473                                .collect::<Vec<_>>();
1474                            sections.sort_by(|a, b| a.range.cmp(&b.range, buffer));
1475                            ContextEvent::SlashCommandFinished {
1476                                output_range: buffer.anchor_after(start)
1477                                    ..buffer.anchor_before(new_end),
1478                                sections,
1479                                run_commands_in_output: output.run_commands_in_text,
1480                            }
1481                        });
1482                        cx.emit(event);
1483                    }
1484                    Err(error) => {
1485                        if let Some(pending_command) =
1486                            this.pending_command_for_position(command_range.start, cx)
1487                        {
1488                            pending_command.status =
1489                                PendingSlashCommandStatus::Error(error.to_string());
1490                            cx.emit(ContextEvent::PendingSlashCommandsUpdated {
1491                                removed: vec![pending_command.source_range.clone()],
1492                                updated: vec![pending_command.clone()],
1493                            });
1494                        }
1495                    }
1496                })
1497                .ok();
1498            }
1499        });
1500
1501        if let Some(pending_command) = self.pending_command_for_position(command_range.start, cx) {
1502            pending_command.status = PendingSlashCommandStatus::Running {
1503                _task: insert_output_task.shared(),
1504            };
1505            cx.emit(ContextEvent::PendingSlashCommandsUpdated {
1506                removed: vec![pending_command.source_range.clone()],
1507                updated: vec![pending_command.clone()],
1508            });
1509        }
1510    }
1511
1512    fn remaining_tokens(&self, cx: &AppContext) -> Option<isize> {
1513        let model = CompletionProvider::global(cx).model();
1514        Some(model.max_token_count() as isize - self.token_count? as isize)
1515    }
1516
1517    fn completion_provider_changed(&mut self, cx: &mut ModelContext<Self>) {
1518        self.count_remaining_tokens(cx);
1519    }
1520
1521    fn assist(
1522        &mut self,
1523        selected_messages: HashSet<MessageId>,
1524        cx: &mut ModelContext<Self>,
1525    ) -> Vec<MessageAnchor> {
1526        let mut user_messages = Vec::new();
1527
1528        let last_message_id = if let Some(last_message_id) =
1529            self.message_anchors.iter().rev().find_map(|message| {
1530                message
1531                    .start
1532                    .is_valid(self.buffer.read(cx))
1533                    .then_some(message.id)
1534            }) {
1535            last_message_id
1536        } else {
1537            return Default::default();
1538        };
1539
1540        let mut should_assist = false;
1541        for selected_message_id in selected_messages {
1542            let selected_message_role =
1543                if let Some(metadata) = self.messages_metadata.get(&selected_message_id) {
1544                    metadata.role
1545                } else {
1546                    continue;
1547                };
1548
1549            if selected_message_role == Role::Assistant {
1550                if let Some(user_message) = self.insert_message_after(
1551                    selected_message_id,
1552                    Role::User,
1553                    MessageStatus::Done,
1554                    cx,
1555                ) {
1556                    user_messages.push(user_message);
1557                }
1558            } else {
1559                should_assist = true;
1560            }
1561        }
1562
1563        if should_assist {
1564            if !CompletionProvider::global(cx).is_authenticated() {
1565                log::info!("completion provider has no credentials");
1566                return Default::default();
1567            }
1568
1569            let request = self.to_completion_request(cx);
1570            let stream = CompletionProvider::global(cx).complete(request);
1571            let assistant_message = self
1572                .insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
1573                .unwrap();
1574
1575            // Queue up the user's next reply.
1576            let user_message = self
1577                .insert_message_after(assistant_message.id, Role::User, MessageStatus::Done, cx)
1578                .unwrap();
1579            user_messages.push(user_message);
1580
1581            let task = cx.spawn({
1582                |this, mut cx| async move {
1583                    let assistant_message_id = assistant_message.id;
1584                    let mut response_latency = None;
1585                    let stream_completion = async {
1586                        let request_start = Instant::now();
1587                        let mut messages = stream.await?;
1588
1589                        while let Some(message) = messages.next().await {
1590                            if response_latency.is_none() {
1591                                response_latency = Some(request_start.elapsed());
1592                            }
1593                            let text = message?;
1594
1595                            this.update(&mut cx, |this, cx| {
1596                                let message_ix = this
1597                                    .message_anchors
1598                                    .iter()
1599                                    .position(|message| message.id == assistant_message_id)?;
1600                                let message_range = this.buffer.update(cx, |buffer, cx| {
1601                                    let message_start_offset =
1602                                        this.message_anchors[message_ix].start.to_offset(buffer);
1603                                    let message_old_end_offset = this.message_anchors
1604                                        [message_ix + 1..]
1605                                        .iter()
1606                                        .find(|message| message.start.is_valid(buffer))
1607                                        .map_or(buffer.len(), |message| {
1608                                            message.start.to_offset(buffer).saturating_sub(1)
1609                                        });
1610                                    let message_new_end_offset =
1611                                        message_old_end_offset + text.len();
1612                                    buffer.edit(
1613                                        [(message_old_end_offset..message_old_end_offset, text)],
1614                                        None,
1615                                        cx,
1616                                    );
1617                                    message_start_offset..message_new_end_offset
1618                                });
1619                                this.reparse_edit_suggestions_in_range(message_range, cx);
1620                                cx.emit(ContextEvent::StreamedCompletion);
1621
1622                                Some(())
1623                            })?;
1624                            smol::future::yield_now().await;
1625                        }
1626
1627                        this.update(&mut cx, |this, cx| {
1628                            this.pending_completions
1629                                .retain(|completion| completion.id != this.completion_count);
1630                            this.summarize(cx);
1631                        })?;
1632
1633                        anyhow::Ok(())
1634                    };
1635
1636                    let result = stream_completion.await;
1637
1638                    this.update(&mut cx, |this, cx| {
1639                        if let Some(metadata) =
1640                            this.messages_metadata.get_mut(&assistant_message.id)
1641                        {
1642                            let error_message = result
1643                                .err()
1644                                .map(|error| error.to_string().trim().to_string());
1645                            if let Some(error_message) = error_message.as_ref() {
1646                                metadata.status =
1647                                    MessageStatus::Error(SharedString::from(error_message.clone()));
1648                            } else {
1649                                metadata.status = MessageStatus::Done;
1650                            }
1651
1652                            if let Some(telemetry) = this.telemetry.as_ref() {
1653                                let model = CompletionProvider::global(cx).model();
1654                                telemetry.report_assistant_event(
1655                                    this.id.clone(),
1656                                    AssistantKind::Panel,
1657                                    model.telemetry_id(),
1658                                    response_latency,
1659                                    error_message,
1660                                );
1661                            }
1662
1663                            cx.emit(ContextEvent::MessagesEdited);
1664                        }
1665                    })
1666                    .ok();
1667                }
1668            });
1669
1670            self.pending_completions.push(PendingCompletion {
1671                id: post_inc(&mut self.completion_count),
1672                _task: task,
1673            });
1674        }
1675
1676        user_messages
1677    }
1678
1679    pub fn to_completion_request(&self, cx: &AppContext) -> LanguageModelRequest {
1680        let messages = self
1681            .messages(cx)
1682            .filter(|message| matches!(message.status, MessageStatus::Done))
1683            .map(|message| message.to_request_message(self.buffer.read(cx)));
1684
1685        LanguageModelRequest {
1686            model: CompletionProvider::global(cx).model(),
1687            messages: messages.collect(),
1688            stop: vec![],
1689            temperature: 1.0,
1690        }
1691    }
1692
1693    fn cancel_last_assist(&mut self) -> bool {
1694        self.pending_completions.pop().is_some()
1695    }
1696
1697    fn cycle_message_roles(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) {
1698        for id in ids {
1699            if let Some(metadata) = self.messages_metadata.get_mut(&id) {
1700                metadata.role.cycle();
1701                cx.emit(ContextEvent::MessagesEdited);
1702                cx.notify();
1703            }
1704        }
1705    }
1706
1707    fn insert_message_after(
1708        &mut self,
1709        message_id: MessageId,
1710        role: Role,
1711        status: MessageStatus,
1712        cx: &mut ModelContext<Self>,
1713    ) -> Option<MessageAnchor> {
1714        if let Some(prev_message_ix) = self
1715            .message_anchors
1716            .iter()
1717            .position(|message| message.id == message_id)
1718        {
1719            // Find the next valid message after the one we were given.
1720            let mut next_message_ix = prev_message_ix + 1;
1721            while let Some(next_message) = self.message_anchors.get(next_message_ix) {
1722                if next_message.start.is_valid(self.buffer.read(cx)) {
1723                    break;
1724                }
1725                next_message_ix += 1;
1726            }
1727
1728            let start = self.buffer.update(cx, |buffer, cx| {
1729                let offset = self
1730                    .message_anchors
1731                    .get(next_message_ix)
1732                    .map_or(buffer.len(), |message| message.start.to_offset(buffer) - 1);
1733                buffer.edit([(offset..offset, "\n")], None, cx);
1734                buffer.anchor_before(offset + 1)
1735            });
1736            let message = MessageAnchor {
1737                id: MessageId(post_inc(&mut self.next_message_id.0)),
1738                start,
1739            };
1740            self.message_anchors
1741                .insert(next_message_ix, message.clone());
1742            self.messages_metadata
1743                .insert(message.id, MessageMetadata { role, status });
1744            cx.emit(ContextEvent::MessagesEdited);
1745            Some(message)
1746        } else {
1747            None
1748        }
1749    }
1750
1751    fn split_message(
1752        &mut self,
1753        range: Range<usize>,
1754        cx: &mut ModelContext<Self>,
1755    ) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
1756        let start_message = self.message_for_offset(range.start, cx);
1757        let end_message = self.message_for_offset(range.end, cx);
1758        if let Some((start_message, end_message)) = start_message.zip(end_message) {
1759            // Prevent splitting when range spans multiple messages.
1760            if start_message.id != end_message.id {
1761                return (None, None);
1762            }
1763
1764            let message = start_message;
1765            let role = message.role;
1766            let mut edited_buffer = false;
1767
1768            let mut suffix_start = None;
1769            if range.start > message.offset_range.start && range.end < message.offset_range.end - 1
1770            {
1771                if self.buffer.read(cx).chars_at(range.end).next() == Some('\n') {
1772                    suffix_start = Some(range.end + 1);
1773                } else if self.buffer.read(cx).reversed_chars_at(range.end).next() == Some('\n') {
1774                    suffix_start = Some(range.end);
1775                }
1776            }
1777
1778            let suffix = if let Some(suffix_start) = suffix_start {
1779                MessageAnchor {
1780                    id: MessageId(post_inc(&mut self.next_message_id.0)),
1781                    start: self.buffer.read(cx).anchor_before(suffix_start),
1782                }
1783            } else {
1784                self.buffer.update(cx, |buffer, cx| {
1785                    buffer.edit([(range.end..range.end, "\n")], None, cx);
1786                });
1787                edited_buffer = true;
1788                MessageAnchor {
1789                    id: MessageId(post_inc(&mut self.next_message_id.0)),
1790                    start: self.buffer.read(cx).anchor_before(range.end + 1),
1791                }
1792            };
1793
1794            self.message_anchors
1795                .insert(message.index_range.end + 1, suffix.clone());
1796            self.messages_metadata.insert(
1797                suffix.id,
1798                MessageMetadata {
1799                    role,
1800                    status: MessageStatus::Done,
1801                },
1802            );
1803
1804            let new_messages =
1805                if range.start == range.end || range.start == message.offset_range.start {
1806                    (None, Some(suffix))
1807                } else {
1808                    let mut prefix_end = None;
1809                    if range.start > message.offset_range.start
1810                        && range.end < message.offset_range.end - 1
1811                    {
1812                        if self.buffer.read(cx).chars_at(range.start).next() == Some('\n') {
1813                            prefix_end = Some(range.start + 1);
1814                        } else if self.buffer.read(cx).reversed_chars_at(range.start).next()
1815                            == Some('\n')
1816                        {
1817                            prefix_end = Some(range.start);
1818                        }
1819                    }
1820
1821                    let selection = if let Some(prefix_end) = prefix_end {
1822                        cx.emit(ContextEvent::MessagesEdited);
1823                        MessageAnchor {
1824                            id: MessageId(post_inc(&mut self.next_message_id.0)),
1825                            start: self.buffer.read(cx).anchor_before(prefix_end),
1826                        }
1827                    } else {
1828                        self.buffer.update(cx, |buffer, cx| {
1829                            buffer.edit([(range.start..range.start, "\n")], None, cx)
1830                        });
1831                        edited_buffer = true;
1832                        MessageAnchor {
1833                            id: MessageId(post_inc(&mut self.next_message_id.0)),
1834                            start: self.buffer.read(cx).anchor_before(range.end + 1),
1835                        }
1836                    };
1837
1838                    self.message_anchors
1839                        .insert(message.index_range.end + 1, selection.clone());
1840                    self.messages_metadata.insert(
1841                        selection.id,
1842                        MessageMetadata {
1843                            role,
1844                            status: MessageStatus::Done,
1845                        },
1846                    );
1847                    (Some(selection), Some(suffix))
1848                };
1849
1850            if !edited_buffer {
1851                cx.emit(ContextEvent::MessagesEdited);
1852            }
1853            new_messages
1854        } else {
1855            (None, None)
1856        }
1857    }
1858
1859    fn summarize(&mut self, cx: &mut ModelContext<Self>) {
1860        if self.message_anchors.len() >= 2 && self.summary.is_none() {
1861            if !CompletionProvider::global(cx).is_authenticated() {
1862                return;
1863            }
1864
1865            let messages = self
1866                .messages(cx)
1867                .map(|message| message.to_request_message(self.buffer.read(cx)))
1868                .chain(Some(LanguageModelRequestMessage {
1869                    role: Role::User,
1870                    content: "Summarize the context into a short title without punctuation.".into(),
1871                }));
1872            let request = LanguageModelRequest {
1873                model: CompletionProvider::global(cx).model(),
1874                messages: messages.collect(),
1875                stop: vec![],
1876                temperature: 1.0,
1877            };
1878
1879            let stream = CompletionProvider::global(cx).complete(request);
1880            self.pending_summary = cx.spawn(|this, mut cx| {
1881                async move {
1882                    let mut messages = stream.await?;
1883
1884                    while let Some(message) = messages.next().await {
1885                        let text = message?;
1886                        let mut lines = text.lines();
1887                        this.update(&mut cx, |this, cx| {
1888                            let summary = this.summary.get_or_insert(Default::default());
1889                            summary.text.extend(lines.next());
1890                            cx.emit(ContextEvent::SummaryChanged);
1891                        })?;
1892
1893                        // Stop if the LLM generated multiple lines.
1894                        if lines.next().is_some() {
1895                            break;
1896                        }
1897                    }
1898
1899                    this.update(&mut cx, |this, cx| {
1900                        if let Some(summary) = this.summary.as_mut() {
1901                            summary.done = true;
1902                            cx.emit(ContextEvent::SummaryChanged);
1903                        }
1904                    })?;
1905
1906                    anyhow::Ok(())
1907                }
1908                .log_err()
1909            });
1910        }
1911    }
1912
1913    fn message_for_offset(&self, offset: usize, cx: &AppContext) -> Option<Message> {
1914        self.messages_for_offsets([offset], cx).pop()
1915    }
1916
1917    fn messages_for_offsets(
1918        &self,
1919        offsets: impl IntoIterator<Item = usize>,
1920        cx: &AppContext,
1921    ) -> Vec<Message> {
1922        let mut result = Vec::new();
1923
1924        let mut messages = self.messages(cx).peekable();
1925        let mut offsets = offsets.into_iter().peekable();
1926        let mut current_message = messages.next();
1927        while let Some(offset) = offsets.next() {
1928            // Locate the message that contains the offset.
1929            while current_message.as_ref().map_or(false, |message| {
1930                !message.offset_range.contains(&offset) && messages.peek().is_some()
1931            }) {
1932                current_message = messages.next();
1933            }
1934            let Some(message) = current_message.as_ref() else {
1935                break;
1936            };
1937
1938            // Skip offsets that are in the same message.
1939            while offsets.peek().map_or(false, |offset| {
1940                message.offset_range.contains(offset) || messages.peek().is_none()
1941            }) {
1942                offsets.next();
1943            }
1944
1945            result.push(message.clone());
1946        }
1947        result
1948    }
1949
1950    fn messages<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Message> {
1951        let buffer = self.buffer.read(cx);
1952        let mut message_anchors = self.message_anchors.iter().enumerate().peekable();
1953        iter::from_fn(move || {
1954            if let Some((start_ix, message_anchor)) = message_anchors.next() {
1955                let metadata = self.messages_metadata.get(&message_anchor.id)?;
1956                let message_start = message_anchor.start.to_offset(buffer);
1957                let mut message_end = None;
1958                let mut end_ix = start_ix;
1959                while let Some((_, next_message)) = message_anchors.peek() {
1960                    if next_message.start.is_valid(buffer) {
1961                        message_end = Some(next_message.start);
1962                        break;
1963                    } else {
1964                        end_ix += 1;
1965                        message_anchors.next();
1966                    }
1967                }
1968                let message_end = message_end
1969                    .unwrap_or(language::Anchor::MAX)
1970                    .to_offset(buffer);
1971
1972                return Some(Message {
1973                    index_range: start_ix..end_ix,
1974                    offset_range: message_start..message_end,
1975                    id: message_anchor.id,
1976                    anchor: message_anchor.start,
1977                    role: metadata.role,
1978                    status: metadata.status.clone(),
1979                });
1980            }
1981            None
1982        })
1983    }
1984
1985    fn save(
1986        &mut self,
1987        debounce: Option<Duration>,
1988        fs: Arc<dyn Fs>,
1989        cx: &mut ModelContext<Context>,
1990    ) {
1991        self.pending_save = cx.spawn(|this, mut cx| async move {
1992            if let Some(debounce) = debounce {
1993                cx.background_executor().timer(debounce).await;
1994            }
1995
1996            let (old_path, summary) = this.read_with(&cx, |this, _| {
1997                let path = this.path.clone();
1998                let summary = if let Some(summary) = this.summary.as_ref() {
1999                    if summary.done {
2000                        Some(summary.text.clone())
2001                    } else {
2002                        None
2003                    }
2004                } else {
2005                    None
2006                };
2007                (path, summary)
2008            })?;
2009
2010            if let Some(summary) = summary {
2011                let context = this.read_with(&cx, |this, cx| this.serialize(cx))?;
2012                let path = if let Some(old_path) = old_path {
2013                    old_path
2014                } else {
2015                    let mut discriminant = 1;
2016                    let mut new_path;
2017                    loop {
2018                        new_path = CONTEXTS_DIR.join(&format!(
2019                            "{} - {}.zed.json",
2020                            summary.trim(),
2021                            discriminant
2022                        ));
2023                        if fs.is_file(&new_path).await {
2024                            discriminant += 1;
2025                        } else {
2026                            break;
2027                        }
2028                    }
2029                    new_path
2030                };
2031
2032                fs.create_dir(CONTEXTS_DIR.as_ref()).await?;
2033                fs.atomic_write(path.clone(), serde_json::to_string(&context).unwrap())
2034                    .await?;
2035                this.update(&mut cx, |this, _| this.path = Some(path))?;
2036            }
2037
2038            Ok(())
2039        });
2040    }
2041}
2042
2043#[derive(Debug)]
2044enum EditParsingState {
2045    None,
2046    InOldText {
2047        path: PathBuf,
2048        start_offset: usize,
2049        old_text_start_offset: usize,
2050    },
2051    InNewText {
2052        path: PathBuf,
2053        start_offset: usize,
2054        old_text_range: Range<usize>,
2055        new_text_start_offset: usize,
2056    },
2057}
2058
2059#[derive(Clone, Debug, PartialEq)]
2060struct EditSuggestion {
2061    source_range: Range<language::Anchor>,
2062    full_path: PathBuf,
2063}
2064
2065struct ParsedEditSuggestion {
2066    path: PathBuf,
2067    outer_range: Range<usize>,
2068    old_text_range: Range<usize>,
2069    new_text_range: Range<usize>,
2070}
2071
2072fn parse_next_edit_suggestion(lines: &mut rope::Lines) -> Option<ParsedEditSuggestion> {
2073    let mut state = EditParsingState::None;
2074    loop {
2075        let offset = lines.offset();
2076        let message_line = lines.next()?;
2077        match state {
2078            EditParsingState::None => {
2079                if let Some(rest) = message_line.strip_prefix("```edit ") {
2080                    let path = rest.trim();
2081                    if !path.is_empty() {
2082                        state = EditParsingState::InOldText {
2083                            path: PathBuf::from(path),
2084                            start_offset: offset,
2085                            old_text_start_offset: lines.offset(),
2086                        };
2087                    }
2088                }
2089            }
2090            EditParsingState::InOldText {
2091                path,
2092                start_offset,
2093                old_text_start_offset,
2094            } => {
2095                if message_line == "---" {
2096                    state = EditParsingState::InNewText {
2097                        path,
2098                        start_offset,
2099                        old_text_range: old_text_start_offset..offset,
2100                        new_text_start_offset: lines.offset(),
2101                    };
2102                } else {
2103                    state = EditParsingState::InOldText {
2104                        path,
2105                        start_offset,
2106                        old_text_start_offset,
2107                    };
2108                }
2109            }
2110            EditParsingState::InNewText {
2111                path,
2112                start_offset,
2113                old_text_range,
2114                new_text_start_offset,
2115            } => {
2116                if message_line == "```" {
2117                    return Some(ParsedEditSuggestion {
2118                        path,
2119                        outer_range: start_offset..offset + "```".len(),
2120                        old_text_range,
2121                        new_text_range: new_text_start_offset..offset,
2122                    });
2123                } else {
2124                    state = EditParsingState::InNewText {
2125                        path,
2126                        start_offset,
2127                        old_text_range,
2128                        new_text_start_offset,
2129                    };
2130                }
2131            }
2132        }
2133    }
2134}
2135
2136#[derive(Clone)]
2137struct PendingSlashCommand {
2138    name: String,
2139    argument: Option<String>,
2140    status: PendingSlashCommandStatus,
2141    source_range: Range<language::Anchor>,
2142}
2143
2144#[derive(Clone)]
2145enum PendingSlashCommandStatus {
2146    Idle,
2147    Running { _task: Shared<Task<()>> },
2148    Error(String),
2149}
2150
2151struct PendingCompletion {
2152    id: usize,
2153    _task: Task<()>,
2154}
2155
2156enum ContextEditorEvent {
2157    TabContentChanged,
2158}
2159
2160#[derive(Copy, Clone, Debug, PartialEq)]
2161struct ScrollPosition {
2162    offset_before_cursor: gpui::Point<f32>,
2163    cursor: Anchor,
2164}
2165
2166pub struct ContextEditor {
2167    context: Model<Context>,
2168    fs: Arc<dyn Fs>,
2169    workspace: WeakView<Workspace>,
2170    slash_command_registry: Arc<SlashCommandRegistry>,
2171    lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
2172    editor: View<Editor>,
2173    blocks: HashSet<BlockId>,
2174    scroll_position: Option<ScrollPosition>,
2175    pending_slash_command_flaps: HashMap<Range<language::Anchor>, FlapId>,
2176    _subscriptions: Vec<Subscription>,
2177}
2178
2179impl ContextEditor {
2180    fn new(
2181        language_registry: Arc<LanguageRegistry>,
2182        slash_command_registry: Arc<SlashCommandRegistry>,
2183        fs: Arc<dyn Fs>,
2184        workspace: View<Workspace>,
2185        cx: &mut ViewContext<Self>,
2186    ) -> Self {
2187        let telemetry = workspace.read(cx).client().telemetry().clone();
2188        let project = workspace.read(cx).project().clone();
2189        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err();
2190
2191        let context = cx.new_model(|cx| {
2192            Context::new(
2193                language_registry,
2194                slash_command_registry,
2195                Some(telemetry),
2196                cx,
2197            )
2198        });
2199
2200        let mut this = Self::for_context(context, fs, workspace, lsp_adapter_delegate, cx);
2201        this.insert_default_prompt(cx);
2202        this
2203    }
2204
2205    fn for_context(
2206        context: Model<Context>,
2207        fs: Arc<dyn Fs>,
2208        workspace: View<Workspace>,
2209        lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
2210        cx: &mut ViewContext<Self>,
2211    ) -> Self {
2212        let slash_command_registry = context.read(cx).slash_command_registry.clone();
2213
2214        let completion_provider = SlashCommandCompletionProvider::new(
2215            slash_command_registry.clone(),
2216            Some(cx.view().downgrade()),
2217            Some(workspace.downgrade()),
2218        );
2219
2220        let editor = cx.new_view(|cx| {
2221            let mut editor = Editor::for_buffer(context.read(cx).buffer.clone(), None, cx);
2222            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
2223            editor.set_show_line_numbers(false, cx);
2224            editor.set_show_git_diff_gutter(false, cx);
2225            editor.set_show_code_actions(false, cx);
2226            editor.set_show_wrap_guides(false, cx);
2227            editor.set_show_indent_guides(false, cx);
2228            editor.set_completion_provider(Box::new(completion_provider));
2229            editor
2230        });
2231
2232        let _subscriptions = vec![
2233            cx.observe(&context, |_, _, cx| cx.notify()),
2234            cx.subscribe(&context, Self::handle_context_event),
2235            cx.subscribe(&editor, Self::handle_editor_event),
2236        ];
2237
2238        let mut this = Self {
2239            context,
2240            editor,
2241            slash_command_registry,
2242            lsp_adapter_delegate,
2243            blocks: Default::default(),
2244            scroll_position: None,
2245            fs,
2246            workspace: workspace.downgrade(),
2247            pending_slash_command_flaps: HashMap::default(),
2248            _subscriptions,
2249        };
2250        this.update_message_headers(cx);
2251        this
2252    }
2253
2254    fn insert_default_prompt(&mut self, cx: &mut ViewContext<Self>) {
2255        let command_name = DefaultSlashCommand.name();
2256        self.editor.update(cx, |editor, cx| {
2257            editor.insert(&format!("/{command_name}"), cx)
2258        });
2259        self.split(&Split, cx);
2260        let command = self.context.update(cx, |context, cx| {
2261            context
2262                .messages_metadata
2263                .get_mut(&MessageId::default())
2264                .unwrap()
2265                .role = Role::System;
2266            context.reparse_slash_commands(cx);
2267            context.pending_slash_commands[0].clone()
2268        });
2269
2270        self.run_command(
2271            command.source_range,
2272            &command.name,
2273            command.argument.as_deref(),
2274            false,
2275            self.workspace.clone(),
2276            cx,
2277        );
2278    }
2279
2280    fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
2281        let cursors = self.cursors(cx);
2282
2283        let user_messages = self.context.update(cx, |context, cx| {
2284            let selected_messages = context
2285                .messages_for_offsets(cursors, cx)
2286                .into_iter()
2287                .map(|message| message.id)
2288                .collect();
2289            context.assist(selected_messages, cx)
2290        });
2291        let new_selections = user_messages
2292            .iter()
2293            .map(|message| {
2294                let cursor = message
2295                    .start
2296                    .to_offset(self.context.read(cx).buffer.read(cx));
2297                cursor..cursor
2298            })
2299            .collect::<Vec<_>>();
2300        if !new_selections.is_empty() {
2301            self.editor.update(cx, |editor, cx| {
2302                editor.change_selections(
2303                    Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
2304                    cx,
2305                    |selections| selections.select_ranges(new_selections),
2306                );
2307            });
2308            // Avoid scrolling to the new cursor position so the assistant's output is stable.
2309            cx.defer(|this, _| this.scroll_position = None);
2310        }
2311    }
2312
2313    fn cancel_last_assist(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
2314        if !self
2315            .context
2316            .update(cx, |context, _| context.cancel_last_assist())
2317        {
2318            cx.propagate();
2319        }
2320    }
2321
2322    fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
2323        let cursors = self.cursors(cx);
2324        self.context.update(cx, |context, cx| {
2325            let messages = context
2326                .messages_for_offsets(cursors, cx)
2327                .into_iter()
2328                .map(|message| message.id)
2329                .collect();
2330            context.cycle_message_roles(messages, cx)
2331        });
2332    }
2333
2334    fn cursors(&self, cx: &AppContext) -> Vec<usize> {
2335        let selections = self.editor.read(cx).selections.all::<usize>(cx);
2336        selections
2337            .into_iter()
2338            .map(|selection| selection.head())
2339            .collect()
2340    }
2341
2342    fn insert_command(&mut self, name: &str, cx: &mut ViewContext<Self>) {
2343        if let Some(command) = self.slash_command_registry.command(name) {
2344            self.editor.update(cx, |editor, cx| {
2345                editor.transact(cx, |editor, cx| {
2346                    editor.change_selections(Some(Autoscroll::fit()), cx, |s| s.try_cancel());
2347                    let snapshot = editor.buffer().read(cx).snapshot(cx);
2348                    let newest_cursor = editor.selections.newest::<Point>(cx).head();
2349                    if newest_cursor.column > 0
2350                        || snapshot
2351                            .chars_at(newest_cursor)
2352                            .next()
2353                            .map_or(false, |ch| ch != '\n')
2354                    {
2355                        editor.move_to_end_of_line(
2356                            &MoveToEndOfLine {
2357                                stop_at_soft_wraps: false,
2358                            },
2359                            cx,
2360                        );
2361                        editor.newline(&Newline, cx);
2362                    }
2363
2364                    editor.insert(&format!("/{name}"), cx);
2365                    if command.requires_argument() {
2366                        editor.insert(" ", cx);
2367                        editor.show_completions(&ShowCompletions, cx);
2368                    }
2369                });
2370            });
2371            if !command.requires_argument() {
2372                self.confirm_command(&ConfirmCommand, cx);
2373            }
2374        }
2375    }
2376
2377    pub fn confirm_command(&mut self, _: &ConfirmCommand, cx: &mut ViewContext<Self>) {
2378        let selections = self.editor.read(cx).selections.disjoint_anchors();
2379        let mut commands_by_range = HashMap::default();
2380        let workspace = self.workspace.clone();
2381        self.context.update(cx, |context, cx| {
2382            context.reparse_slash_commands(cx);
2383            for selection in selections.iter() {
2384                if let Some(command) =
2385                    context.pending_command_for_position(selection.head().text_anchor, cx)
2386                {
2387                    commands_by_range
2388                        .entry(command.source_range.clone())
2389                        .or_insert_with(|| command.clone());
2390                }
2391            }
2392        });
2393
2394        if commands_by_range.is_empty() {
2395            cx.propagate();
2396        } else {
2397            for command in commands_by_range.into_values() {
2398                self.run_command(
2399                    command.source_range,
2400                    &command.name,
2401                    command.argument.as_deref(),
2402                    true,
2403                    workspace.clone(),
2404                    cx,
2405                );
2406            }
2407            cx.stop_propagation();
2408        }
2409    }
2410
2411    pub fn run_command(
2412        &mut self,
2413        command_range: Range<language::Anchor>,
2414        name: &str,
2415        argument: Option<&str>,
2416        insert_trailing_newline: bool,
2417        workspace: WeakView<Workspace>,
2418        cx: &mut ViewContext<Self>,
2419    ) {
2420        if let Some(command) = self.slash_command_registry.command(name) {
2421            if let Some(lsp_adapter_delegate) = self.lsp_adapter_delegate.clone() {
2422                let argument = argument.map(ToString::to_string);
2423                let output = command.run(argument.as_deref(), workspace, lsp_adapter_delegate, cx);
2424                self.context.update(cx, |context, cx| {
2425                    context.insert_command_output(
2426                        command_range,
2427                        output,
2428                        insert_trailing_newline,
2429                        cx,
2430                    )
2431                });
2432            }
2433        }
2434    }
2435
2436    fn handle_context_event(
2437        &mut self,
2438        _: Model<Context>,
2439        event: &ContextEvent,
2440        cx: &mut ViewContext<Self>,
2441    ) {
2442        let context_editor = cx.view().downgrade();
2443
2444        match event {
2445            ContextEvent::MessagesEdited => {
2446                self.update_message_headers(cx);
2447                self.context.update(cx, |context, cx| {
2448                    context.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
2449                });
2450            }
2451            ContextEvent::EditSuggestionsChanged => {
2452                self.editor.update(cx, |editor, cx| {
2453                    let buffer = editor.buffer().read(cx).snapshot(cx);
2454                    let excerpt_id = *buffer.as_singleton().unwrap().0;
2455                    let context = self.context.read(cx);
2456                    let highlighted_rows = context
2457                        .edit_suggestions
2458                        .iter()
2459                        .map(|suggestion| {
2460                            let start = buffer
2461                                .anchor_in_excerpt(excerpt_id, suggestion.source_range.start)
2462                                .unwrap();
2463                            let end = buffer
2464                                .anchor_in_excerpt(excerpt_id, suggestion.source_range.end)
2465                                .unwrap();
2466                            start..=end
2467                        })
2468                        .collect::<Vec<_>>();
2469
2470                    editor.clear_row_highlights::<EditSuggestion>();
2471                    for range in highlighted_rows {
2472                        editor.highlight_rows::<EditSuggestion>(
2473                            range,
2474                            Some(
2475                                cx.theme()
2476                                    .colors()
2477                                    .editor_document_highlight_read_background,
2478                            ),
2479                            false,
2480                            cx,
2481                        );
2482                    }
2483                });
2484            }
2485            ContextEvent::SummaryChanged => {
2486                cx.emit(ContextEditorEvent::TabContentChanged);
2487                self.context.update(cx, |context, cx| {
2488                    context.save(None, self.fs.clone(), cx);
2489                });
2490            }
2491            ContextEvent::StreamedCompletion => {
2492                self.editor.update(cx, |editor, cx| {
2493                    if let Some(scroll_position) = self.scroll_position {
2494                        let snapshot = editor.snapshot(cx);
2495                        let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
2496                        let scroll_top =
2497                            cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
2498                        editor.set_scroll_position(
2499                            point(scroll_position.offset_before_cursor.x, scroll_top),
2500                            cx,
2501                        );
2502                    }
2503                });
2504            }
2505            ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
2506                self.editor.update(cx, |editor, cx| {
2507                    let buffer = editor.buffer().read(cx).snapshot(cx);
2508                    let excerpt_id = *buffer.as_singleton().unwrap().0;
2509
2510                    editor.remove_flaps(
2511                        removed
2512                            .iter()
2513                            .filter_map(|range| self.pending_slash_command_flaps.remove(range)),
2514                        cx,
2515                    );
2516
2517                    let flap_ids = editor.insert_flaps(
2518                        updated.iter().map(|command| {
2519                            let workspace = self.workspace.clone();
2520                            let confirm_command = Arc::new({
2521                                let context_editor = context_editor.clone();
2522                                let command = command.clone();
2523                                move |cx: &mut WindowContext| {
2524                                    context_editor
2525                                        .update(cx, |context_editor, cx| {
2526                                            context_editor.run_command(
2527                                                command.source_range.clone(),
2528                                                &command.name,
2529                                                command.argument.as_deref(),
2530                                                false,
2531                                                workspace.clone(),
2532                                                cx,
2533                                            );
2534                                        })
2535                                        .ok();
2536                                }
2537                            });
2538                            let placeholder = FoldPlaceholder {
2539                                render: Arc::new(move |_, _, _| Empty.into_any()),
2540                                constrain_width: false,
2541                                merge_adjacent: false,
2542                            };
2543                            let render_toggle = {
2544                                let confirm_command = confirm_command.clone();
2545                                let command = command.clone();
2546                                move |row, _, _, _cx: &mut WindowContext| {
2547                                    render_pending_slash_command_gutter_decoration(
2548                                        row,
2549                                        command.status.clone(),
2550                                        confirm_command.clone(),
2551                                    )
2552                                }
2553                            };
2554                            let render_trailer =
2555                                |_row, _unfold, _cx: &mut WindowContext| Empty.into_any();
2556
2557                            let start = buffer
2558                                .anchor_in_excerpt(excerpt_id, command.source_range.start)
2559                                .unwrap();
2560                            let end = buffer
2561                                .anchor_in_excerpt(excerpt_id, command.source_range.end)
2562                                .unwrap();
2563                            Flap::new(start..end, placeholder, render_toggle, render_trailer)
2564                        }),
2565                        cx,
2566                    );
2567
2568                    self.pending_slash_command_flaps.extend(
2569                        updated
2570                            .iter()
2571                            .map(|command| command.source_range.clone())
2572                            .zip(flap_ids),
2573                    );
2574                })
2575            }
2576            ContextEvent::SlashCommandFinished {
2577                output_range,
2578                sections,
2579                run_commands_in_output,
2580            } => {
2581                self.insert_slash_command_output_sections(sections.iter().cloned(), cx);
2582
2583                if *run_commands_in_output {
2584                    let commands = self.context.update(cx, |context, cx| {
2585                        context.reparse_slash_commands(cx);
2586                        context
2587                            .pending_commands_for_range(output_range.clone(), cx)
2588                            .to_vec()
2589                    });
2590
2591                    for command in commands {
2592                        self.run_command(
2593                            command.source_range,
2594                            &command.name,
2595                            command.argument.as_deref(),
2596                            false,
2597                            self.workspace.clone(),
2598                            cx,
2599                        );
2600                    }
2601                }
2602            }
2603        }
2604    }
2605
2606    fn insert_slash_command_output_sections(
2607        &mut self,
2608        sections: impl IntoIterator<Item = SlashCommandOutputSection<language::Anchor>>,
2609        cx: &mut ViewContext<Self>,
2610    ) {
2611        self.editor.update(cx, |editor, cx| {
2612            let buffer = editor.buffer().read(cx).snapshot(cx);
2613            let excerpt_id = *buffer.as_singleton().unwrap().0;
2614            let mut buffer_rows_to_fold = BTreeSet::new();
2615            let mut flaps = Vec::new();
2616            for section in sections {
2617                let start = buffer
2618                    .anchor_in_excerpt(excerpt_id, section.range.start)
2619                    .unwrap();
2620                let end = buffer
2621                    .anchor_in_excerpt(excerpt_id, section.range.end)
2622                    .unwrap();
2623                let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2624                buffer_rows_to_fold.insert(buffer_row);
2625                flaps.push(Flap::new(
2626                    start..end,
2627                    FoldPlaceholder {
2628                        render: Arc::new({
2629                            let editor = cx.view().downgrade();
2630                            let render_placeholder = section.render_placeholder.clone();
2631                            move |fold_id, fold_range, cx| {
2632                                let editor = editor.clone();
2633                                let unfold = Arc::new(move |cx: &mut WindowContext| {
2634                                    editor
2635                                        .update(cx, |editor, cx| {
2636                                            let buffer_start = fold_range
2637                                                .start
2638                                                .to_point(&editor.buffer().read(cx).read(cx));
2639                                            let buffer_row = MultiBufferRow(buffer_start.row);
2640                                            editor.unfold_at(&UnfoldAt { buffer_row }, cx);
2641                                        })
2642                                        .ok();
2643                                });
2644                                render_placeholder(fold_id.into(), unfold, cx)
2645                            }
2646                        }),
2647                        constrain_width: false,
2648                        merge_adjacent: false,
2649                    },
2650                    render_slash_command_output_toggle,
2651                    |_, _, _| Empty.into_any_element(),
2652                ));
2653            }
2654
2655            editor.insert_flaps(flaps, cx);
2656
2657            for buffer_row in buffer_rows_to_fold.into_iter().rev() {
2658                editor.fold_at(&FoldAt { buffer_row }, cx);
2659            }
2660        });
2661    }
2662
2663    fn handle_editor_event(
2664        &mut self,
2665        _: View<Editor>,
2666        event: &EditorEvent,
2667        cx: &mut ViewContext<Self>,
2668    ) {
2669        match event {
2670            EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
2671                let cursor_scroll_position = self.cursor_scroll_position(cx);
2672                if *autoscroll {
2673                    self.scroll_position = cursor_scroll_position;
2674                } else if self.scroll_position != cursor_scroll_position {
2675                    self.scroll_position = None;
2676                }
2677            }
2678            EditorEvent::SelectionsChanged { .. } => {
2679                self.scroll_position = self.cursor_scroll_position(cx);
2680            }
2681            _ => {}
2682        }
2683    }
2684
2685    fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
2686        self.editor.update(cx, |editor, cx| {
2687            let snapshot = editor.snapshot(cx);
2688            let cursor = editor.selections.newest_anchor().head();
2689            let cursor_row = cursor
2690                .to_display_point(&snapshot.display_snapshot)
2691                .row()
2692                .as_f32();
2693            let scroll_position = editor
2694                .scroll_manager
2695                .anchor()
2696                .scroll_position(&snapshot.display_snapshot);
2697
2698            let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
2699            if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
2700                Some(ScrollPosition {
2701                    cursor,
2702                    offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
2703                })
2704            } else {
2705                None
2706            }
2707        })
2708    }
2709
2710    fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
2711        self.editor.update(cx, |editor, cx| {
2712            let buffer = editor.buffer().read(cx).snapshot(cx);
2713            let excerpt_id = *buffer.as_singleton().unwrap().0;
2714            let old_blocks = std::mem::take(&mut self.blocks);
2715            let new_blocks = self
2716                .context
2717                .read(cx)
2718                .messages(cx)
2719                .map(|message| BlockProperties {
2720                    position: buffer
2721                        .anchor_in_excerpt(excerpt_id, message.anchor)
2722                        .unwrap(),
2723                    height: 2,
2724                    style: BlockStyle::Sticky,
2725                    render: Box::new({
2726                        let context = self.context.clone();
2727                        move |cx| {
2728                            let message_id = message.id;
2729                            let sender = ButtonLike::new("role")
2730                                .style(ButtonStyle::Filled)
2731                                .child(match message.role {
2732                                    Role::User => Label::new("You").color(Color::Default),
2733                                    Role::Assistant => Label::new("Assistant").color(Color::Info),
2734                                    Role::System => Label::new("System").color(Color::Warning),
2735                                })
2736                                .tooltip(|cx| {
2737                                    Tooltip::with_meta(
2738                                        "Toggle message role",
2739                                        None,
2740                                        "Available roles: You (User), Assistant, System",
2741                                        cx,
2742                                    )
2743                                })
2744                                .on_click({
2745                                    let context = context.clone();
2746                                    move |_, cx| {
2747                                        context.update(cx, |context, cx| {
2748                                            context.cycle_message_roles(
2749                                                HashSet::from_iter(Some(message_id)),
2750                                                cx,
2751                                            )
2752                                        })
2753                                    }
2754                                });
2755
2756                            h_flex()
2757                                .id(("message_header", message_id.0))
2758                                .pl(cx.gutter_dimensions.full_width())
2759                                .h_11()
2760                                .w_full()
2761                                .relative()
2762                                .gap_1()
2763                                .child(sender)
2764                                .children(
2765                                    if let MessageStatus::Error(error) = message.status.clone() {
2766                                        Some(
2767                                            div()
2768                                                .id("error")
2769                                                .tooltip(move |cx| Tooltip::text(error.clone(), cx))
2770                                                .child(Icon::new(IconName::XCircle)),
2771                                        )
2772                                    } else {
2773                                        None
2774                                    },
2775                                )
2776                                .into_any_element()
2777                        }
2778                    }),
2779                    disposition: BlockDisposition::Above,
2780                })
2781                .collect::<Vec<_>>();
2782
2783            editor.remove_blocks(old_blocks, None, cx);
2784            let ids = editor.insert_blocks(new_blocks, None, cx);
2785            self.blocks = HashSet::from_iter(ids);
2786        });
2787    }
2788
2789    fn quote_selection(
2790        workspace: &mut Workspace,
2791        _: &QuoteSelection,
2792        cx: &mut ViewContext<Workspace>,
2793    ) {
2794        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2795            return;
2796        };
2797        let Some(editor) = workspace
2798            .active_item(cx)
2799            .and_then(|item| item.act_as::<Editor>(cx))
2800        else {
2801            return;
2802        };
2803
2804        let editor = editor.read(cx);
2805        let range = editor.selections.newest::<usize>(cx).range();
2806        let buffer = editor.buffer().read(cx).snapshot(cx);
2807        let start_language = buffer.language_at(range.start);
2808        let end_language = buffer.language_at(range.end);
2809        let language_name = if start_language == end_language {
2810            start_language.map(|language| language.code_fence_block_name())
2811        } else {
2812            None
2813        };
2814        let language_name = language_name.as_deref().unwrap_or("");
2815
2816        let selected_text = buffer.text_for_range(range).collect::<String>();
2817        let text = if selected_text.is_empty() {
2818            None
2819        } else {
2820            Some(if language_name == "markdown" {
2821                selected_text
2822                    .lines()
2823                    .map(|line| format!("> {}", line))
2824                    .collect::<Vec<_>>()
2825                    .join("\n")
2826            } else {
2827                format!("```{language_name}\n{selected_text}\n```")
2828            })
2829        };
2830
2831        // Activate the panel
2832        if !panel.focus_handle(cx).contains_focused(cx) {
2833            workspace.toggle_panel_focus::<AssistantPanel>(cx);
2834        }
2835
2836        if let Some(text) = text {
2837            panel.update(cx, |_, cx| {
2838                // Wait to create a new context until the workspace is no longer
2839                // being updated.
2840                cx.defer(move |panel, cx| {
2841                    if let Some(context) = panel
2842                        .active_context_editor()
2843                        .cloned()
2844                        .or_else(|| panel.new_context(cx))
2845                    {
2846                        context.update(cx, |context, cx| {
2847                            context
2848                                .editor
2849                                .update(cx, |editor, cx| editor.insert(&text, cx))
2850                        });
2851                    };
2852                });
2853            });
2854        }
2855    }
2856
2857    fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
2858        let editor = self.editor.read(cx);
2859        let context = self.context.read(cx);
2860        if editor.selections.count() == 1 {
2861            let selection = editor.selections.newest::<usize>(cx);
2862            let mut copied_text = String::new();
2863            let mut spanned_messages = 0;
2864            for message in context.messages(cx) {
2865                if message.offset_range.start >= selection.range().end {
2866                    break;
2867                } else if message.offset_range.end >= selection.range().start {
2868                    let range = cmp::max(message.offset_range.start, selection.range().start)
2869                        ..cmp::min(message.offset_range.end, selection.range().end);
2870                    if !range.is_empty() {
2871                        spanned_messages += 1;
2872                        write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
2873                        for chunk in context.buffer.read(cx).text_for_range(range) {
2874                            copied_text.push_str(chunk);
2875                        }
2876                        copied_text.push('\n');
2877                    }
2878                }
2879            }
2880
2881            if spanned_messages > 1 {
2882                cx.write_to_clipboard(ClipboardItem::new(copied_text));
2883                return;
2884            }
2885        }
2886
2887        cx.propagate();
2888    }
2889
2890    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
2891        self.context.update(cx, |context, cx| {
2892            let selections = self.editor.read(cx).selections.disjoint_anchors();
2893            for selection in selections.as_ref() {
2894                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
2895                let range = selection
2896                    .map(|endpoint| endpoint.to_offset(&buffer))
2897                    .range();
2898                context.split_message(range, cx);
2899            }
2900        });
2901    }
2902
2903    fn apply_edit(&mut self, _: &ApplyEdit, cx: &mut ViewContext<Self>) {
2904        let Some(workspace) = self.workspace.upgrade() else {
2905            return;
2906        };
2907        let project = workspace.read(cx).project().clone();
2908
2909        struct Edit {
2910            old_text: String,
2911            new_text: String,
2912        }
2913
2914        let context = self.context.read(cx);
2915        let context_buffer = context.buffer.read(cx);
2916        let context_buffer_snapshot = context_buffer.snapshot();
2917
2918        let selections = self.editor.read(cx).selections.disjoint_anchors();
2919        let mut selections = selections.iter().peekable();
2920        let selected_suggestions = context
2921            .edit_suggestions
2922            .iter()
2923            .filter(|suggestion| {
2924                while let Some(selection) = selections.peek() {
2925                    if selection
2926                        .end
2927                        .text_anchor
2928                        .cmp(&suggestion.source_range.start, context_buffer)
2929                        .is_lt()
2930                    {
2931                        selections.next();
2932                        continue;
2933                    }
2934                    if selection
2935                        .start
2936                        .text_anchor
2937                        .cmp(&suggestion.source_range.end, context_buffer)
2938                        .is_gt()
2939                    {
2940                        break;
2941                    }
2942                    return true;
2943                }
2944                false
2945            })
2946            .cloned()
2947            .collect::<Vec<_>>();
2948
2949        let mut opened_buffers: HashMap<PathBuf, Task<Result<Model<Buffer>>>> = HashMap::default();
2950        project.update(cx, |project, cx| {
2951            for suggestion in &selected_suggestions {
2952                opened_buffers
2953                    .entry(suggestion.full_path.clone())
2954                    .or_insert_with(|| {
2955                        project.open_buffer_for_full_path(&suggestion.full_path, cx)
2956                    });
2957            }
2958        });
2959
2960        cx.spawn(|this, mut cx| async move {
2961            let mut buffers_by_full_path = HashMap::default();
2962            for (full_path, buffer) in opened_buffers {
2963                if let Some(buffer) = buffer.await.log_err() {
2964                    buffers_by_full_path.insert(full_path, buffer);
2965                }
2966            }
2967
2968            let mut suggestions_by_buffer = HashMap::default();
2969            cx.update(|cx| {
2970                for suggestion in selected_suggestions {
2971                    if let Some(buffer) = buffers_by_full_path.get(&suggestion.full_path) {
2972                        let (_, edits) = suggestions_by_buffer
2973                            .entry(buffer.clone())
2974                            .or_insert_with(|| (buffer.read(cx).snapshot(), Vec::new()));
2975
2976                        let mut lines = context_buffer_snapshot
2977                            .as_rope()
2978                            .chunks_in_range(
2979                                suggestion.source_range.to_offset(&context_buffer_snapshot),
2980                            )
2981                            .lines();
2982                        if let Some(suggestion) = parse_next_edit_suggestion(&mut lines) {
2983                            let old_text = context_buffer_snapshot
2984                                .text_for_range(suggestion.old_text_range)
2985                                .collect();
2986                            let new_text = context_buffer_snapshot
2987                                .text_for_range(suggestion.new_text_range)
2988                                .collect();
2989                            edits.push(Edit { old_text, new_text });
2990                        }
2991                    }
2992                }
2993            })?;
2994
2995            let edits_by_buffer = cx
2996                .background_executor()
2997                .spawn(async move {
2998                    let mut result = HashMap::default();
2999                    for (buffer, (snapshot, suggestions)) in suggestions_by_buffer {
3000                        let edits =
3001                            result
3002                                .entry(buffer)
3003                                .or_insert(Vec::<(Range<language::Anchor>, _)>::new());
3004                        for suggestion in suggestions {
3005                            if let Some(range) =
3006                                fuzzy_search_lines(snapshot.as_rope(), &suggestion.old_text)
3007                            {
3008                                let edit_start = snapshot.anchor_after(range.start);
3009                                let edit_end = snapshot.anchor_before(range.end);
3010                                if let Err(ix) = edits.binary_search_by(|(range, _)| {
3011                                    range.start.cmp(&edit_start, &snapshot)
3012                                }) {
3013                                    edits.insert(
3014                                        ix,
3015                                        (edit_start..edit_end, suggestion.new_text.clone()),
3016                                    );
3017                                }
3018                            } else {
3019                                log::info!(
3020                                    "assistant edit did not match any text in buffer {:?}",
3021                                    &suggestion.old_text
3022                                );
3023                            }
3024                        }
3025                    }
3026                    result
3027                })
3028                .await;
3029
3030            let mut project_transaction = ProjectTransaction::default();
3031            let (editor, workspace, title) = this.update(&mut cx, |this, cx| {
3032                for (buffer_handle, edits) in edits_by_buffer {
3033                    buffer_handle.update(cx, |buffer, cx| {
3034                        buffer.start_transaction();
3035                        buffer.edit(
3036                            edits,
3037                            Some(AutoindentMode::Block {
3038                                original_indent_columns: Vec::new(),
3039                            }),
3040                            cx,
3041                        );
3042                        buffer.end_transaction(cx);
3043                        if let Some(transaction) = buffer.finalize_last_transaction() {
3044                            project_transaction
3045                                .0
3046                                .insert(buffer_handle.clone(), transaction.clone());
3047                        }
3048                    });
3049                }
3050
3051                (
3052                    this.editor.downgrade(),
3053                    this.workspace.clone(),
3054                    this.title(cx),
3055                )
3056            })?;
3057
3058            Editor::open_project_transaction(
3059                &editor,
3060                workspace,
3061                project_transaction,
3062                format!("Edits from {}", title),
3063                cx,
3064            )
3065            .await
3066        })
3067        .detach_and_log_err(cx);
3068    }
3069
3070    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
3071        self.context
3072            .update(cx, |context, cx| context.save(None, self.fs.clone(), cx));
3073    }
3074
3075    fn title(&self, cx: &AppContext) -> String {
3076        self.context
3077            .read(cx)
3078            .summary
3079            .as_ref()
3080            .map(|summary| summary.text.clone())
3081            .unwrap_or_else(|| "New Context".into())
3082    }
3083}
3084
3085impl EventEmitter<ContextEditorEvent> for ContextEditor {}
3086
3087impl Render for ContextEditor {
3088    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3089        div()
3090            .key_context("ContextEditor")
3091            .capture_action(cx.listener(ContextEditor::cancel_last_assist))
3092            .capture_action(cx.listener(ContextEditor::save))
3093            .capture_action(cx.listener(ContextEditor::copy))
3094            .capture_action(cx.listener(ContextEditor::cycle_message_role))
3095            .capture_action(cx.listener(ContextEditor::confirm_command))
3096            .on_action(cx.listener(ContextEditor::assist))
3097            .on_action(cx.listener(ContextEditor::split))
3098            .on_action(cx.listener(ContextEditor::apply_edit))
3099            .size_full()
3100            .v_flex()
3101            .child(
3102                div()
3103                    .flex_grow()
3104                    .bg(cx.theme().colors().editor_background)
3105                    .child(self.editor.clone()),
3106            )
3107    }
3108}
3109
3110impl FocusableView for ContextEditor {
3111    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3112        self.editor.focus_handle(cx)
3113    }
3114}
3115
3116#[derive(Clone, Debug)]
3117struct MessageAnchor {
3118    id: MessageId,
3119    start: language::Anchor,
3120}
3121
3122#[derive(Clone, Debug)]
3123pub struct Message {
3124    offset_range: Range<usize>,
3125    index_range: Range<usize>,
3126    id: MessageId,
3127    anchor: language::Anchor,
3128    role: Role,
3129    status: MessageStatus,
3130}
3131
3132impl Message {
3133    fn to_request_message(&self, buffer: &Buffer) -> LanguageModelRequestMessage {
3134        LanguageModelRequestMessage {
3135            role: self.role,
3136            content: buffer.text_for_range(self.offset_range.clone()).collect(),
3137        }
3138    }
3139}
3140
3141type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
3142
3143fn render_slash_command_output_toggle(
3144    row: MultiBufferRow,
3145    is_folded: bool,
3146    fold: ToggleFold,
3147    _cx: &mut WindowContext,
3148) -> AnyElement {
3149    IconButton::new(
3150        ("slash-command-output-fold-indicator", row.0),
3151        ui::IconName::ChevronDown,
3152    )
3153    .on_click(move |_e, cx| fold(!is_folded, cx))
3154    .icon_color(ui::Color::Muted)
3155    .icon_size(ui::IconSize::Small)
3156    .selected(is_folded)
3157    .selected_icon(ui::IconName::ChevronRight)
3158    .size(ui::ButtonSize::None)
3159    .into_any_element()
3160}
3161
3162fn render_pending_slash_command_gutter_decoration(
3163    row: MultiBufferRow,
3164    status: PendingSlashCommandStatus,
3165    confirm_command: Arc<dyn Fn(&mut WindowContext)>,
3166) -> AnyElement {
3167    let mut icon = IconButton::new(
3168        ("slash-command-gutter-decoration", row.0),
3169        ui::IconName::TriangleRight,
3170    )
3171    .on_click(move |_e, cx| confirm_command(cx))
3172    .icon_size(ui::IconSize::Small)
3173    .size(ui::ButtonSize::None);
3174
3175    match status {
3176        PendingSlashCommandStatus::Idle => {
3177            icon = icon.icon_color(Color::Muted);
3178        }
3179        PendingSlashCommandStatus::Running { .. } => {
3180            icon = icon.selected(true);
3181        }
3182        PendingSlashCommandStatus::Error(error) => {
3183            icon = icon
3184                .icon_color(Color::Error)
3185                .tooltip(move |cx| Tooltip::text(format!("error: {error}"), cx));
3186        }
3187    }
3188
3189    icon.into_any_element()
3190}
3191
3192fn make_lsp_adapter_delegate(
3193    project: &Model<Project>,
3194    cx: &mut AppContext,
3195) -> Result<Arc<dyn LspAdapterDelegate>> {
3196    project.update(cx, |project, cx| {
3197        // TODO: Find the right worktree.
3198        let worktree = project
3199            .worktrees()
3200            .next()
3201            .ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
3202        Ok(ProjectLspAdapterDelegate::new(project, &worktree, cx) as Arc<dyn LspAdapterDelegate>)
3203    })
3204}
3205
3206#[cfg(test)]
3207mod tests {
3208    use super::*;
3209    use crate::{
3210        slash_command::{active_command, file_command},
3211        FakeCompletionProvider, MessageId,
3212    };
3213    use fs::FakeFs;
3214    use gpui::{AppContext, TestAppContext};
3215    use rope::Rope;
3216    use serde_json::json;
3217    use settings::SettingsStore;
3218    use std::{cell::RefCell, path::Path, rc::Rc};
3219    use unindent::Unindent;
3220    use util::test::marked_text_ranges;
3221
3222    #[gpui::test]
3223    fn test_inserting_and_removing_messages(cx: &mut AppContext) {
3224        let settings_store = SettingsStore::test(cx);
3225        cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
3226        cx.set_global(settings_store);
3227        init(cx);
3228        let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
3229
3230        let context = cx.new_model(|cx| Context::new(registry, Default::default(), None, cx));
3231        let buffer = context.read(cx).buffer.clone();
3232
3233        let message_1 = context.read(cx).message_anchors[0].clone();
3234        assert_eq!(
3235            messages(&context, cx),
3236            vec![(message_1.id, Role::User, 0..0)]
3237        );
3238
3239        let message_2 = context.update(cx, |context, cx| {
3240            context
3241                .insert_message_after(message_1.id, Role::Assistant, MessageStatus::Done, cx)
3242                .unwrap()
3243        });
3244        assert_eq!(
3245            messages(&context, cx),
3246            vec![
3247                (message_1.id, Role::User, 0..1),
3248                (message_2.id, Role::Assistant, 1..1)
3249            ]
3250        );
3251
3252        buffer.update(cx, |buffer, cx| {
3253            buffer.edit([(0..0, "1"), (1..1, "2")], None, cx)
3254        });
3255        assert_eq!(
3256            messages(&context, cx),
3257            vec![
3258                (message_1.id, Role::User, 0..2),
3259                (message_2.id, Role::Assistant, 2..3)
3260            ]
3261        );
3262
3263        let message_3 = context.update(cx, |context, cx| {
3264            context
3265                .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
3266                .unwrap()
3267        });
3268        assert_eq!(
3269            messages(&context, cx),
3270            vec![
3271                (message_1.id, Role::User, 0..2),
3272                (message_2.id, Role::Assistant, 2..4),
3273                (message_3.id, Role::User, 4..4)
3274            ]
3275        );
3276
3277        let message_4 = context.update(cx, |context, cx| {
3278            context
3279                .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
3280                .unwrap()
3281        });
3282        assert_eq!(
3283            messages(&context, cx),
3284            vec![
3285                (message_1.id, Role::User, 0..2),
3286                (message_2.id, Role::Assistant, 2..4),
3287                (message_4.id, Role::User, 4..5),
3288                (message_3.id, Role::User, 5..5),
3289            ]
3290        );
3291
3292        buffer.update(cx, |buffer, cx| {
3293            buffer.edit([(4..4, "C"), (5..5, "D")], None, cx)
3294        });
3295        assert_eq!(
3296            messages(&context, cx),
3297            vec![
3298                (message_1.id, Role::User, 0..2),
3299                (message_2.id, Role::Assistant, 2..4),
3300                (message_4.id, Role::User, 4..6),
3301                (message_3.id, Role::User, 6..7),
3302            ]
3303        );
3304
3305        // Deleting across message boundaries merges the messages.
3306        buffer.update(cx, |buffer, cx| buffer.edit([(1..4, "")], None, cx));
3307        assert_eq!(
3308            messages(&context, cx),
3309            vec![
3310                (message_1.id, Role::User, 0..3),
3311                (message_3.id, Role::User, 3..4),
3312            ]
3313        );
3314
3315        // Undoing the deletion should also undo the merge.
3316        buffer.update(cx, |buffer, cx| buffer.undo(cx));
3317        assert_eq!(
3318            messages(&context, cx),
3319            vec![
3320                (message_1.id, Role::User, 0..2),
3321                (message_2.id, Role::Assistant, 2..4),
3322                (message_4.id, Role::User, 4..6),
3323                (message_3.id, Role::User, 6..7),
3324            ]
3325        );
3326
3327        // Redoing the deletion should also redo the merge.
3328        buffer.update(cx, |buffer, cx| buffer.redo(cx));
3329        assert_eq!(
3330            messages(&context, cx),
3331            vec![
3332                (message_1.id, Role::User, 0..3),
3333                (message_3.id, Role::User, 3..4),
3334            ]
3335        );
3336
3337        // Ensure we can still insert after a merged message.
3338        let message_5 = context.update(cx, |context, cx| {
3339            context
3340                .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
3341                .unwrap()
3342        });
3343        assert_eq!(
3344            messages(&context, cx),
3345            vec![
3346                (message_1.id, Role::User, 0..3),
3347                (message_5.id, Role::System, 3..4),
3348                (message_3.id, Role::User, 4..5)
3349            ]
3350        );
3351    }
3352
3353    #[gpui::test]
3354    fn test_message_splitting(cx: &mut AppContext) {
3355        let settings_store = SettingsStore::test(cx);
3356        cx.set_global(settings_store);
3357        cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
3358        init(cx);
3359        let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
3360
3361        let context = cx.new_model(|cx| Context::new(registry, Default::default(), None, cx));
3362        let buffer = context.read(cx).buffer.clone();
3363
3364        let message_1 = context.read(cx).message_anchors[0].clone();
3365        assert_eq!(
3366            messages(&context, cx),
3367            vec![(message_1.id, Role::User, 0..0)]
3368        );
3369
3370        buffer.update(cx, |buffer, cx| {
3371            buffer.edit([(0..0, "aaa\nbbb\nccc\nddd\n")], None, cx)
3372        });
3373
3374        let (_, message_2) = context.update(cx, |context, cx| context.split_message(3..3, cx));
3375        let message_2 = message_2.unwrap();
3376
3377        // We recycle newlines in the middle of a split message
3378        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\nddd\n");
3379        assert_eq!(
3380            messages(&context, cx),
3381            vec![
3382                (message_1.id, Role::User, 0..4),
3383                (message_2.id, Role::User, 4..16),
3384            ]
3385        );
3386
3387        let (_, message_3) = context.update(cx, |context, cx| context.split_message(3..3, cx));
3388        let message_3 = message_3.unwrap();
3389
3390        // We don't recycle newlines at the end of a split message
3391        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
3392        assert_eq!(
3393            messages(&context, cx),
3394            vec![
3395                (message_1.id, Role::User, 0..4),
3396                (message_3.id, Role::User, 4..5),
3397                (message_2.id, Role::User, 5..17),
3398            ]
3399        );
3400
3401        let (_, message_4) = context.update(cx, |context, cx| context.split_message(9..9, cx));
3402        let message_4 = message_4.unwrap();
3403        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
3404        assert_eq!(
3405            messages(&context, cx),
3406            vec![
3407                (message_1.id, Role::User, 0..4),
3408                (message_3.id, Role::User, 4..5),
3409                (message_2.id, Role::User, 5..9),
3410                (message_4.id, Role::User, 9..17),
3411            ]
3412        );
3413
3414        let (_, message_5) = context.update(cx, |context, cx| context.split_message(9..9, cx));
3415        let message_5 = message_5.unwrap();
3416        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\nddd\n");
3417        assert_eq!(
3418            messages(&context, cx),
3419            vec![
3420                (message_1.id, Role::User, 0..4),
3421                (message_3.id, Role::User, 4..5),
3422                (message_2.id, Role::User, 5..9),
3423                (message_4.id, Role::User, 9..10),
3424                (message_5.id, Role::User, 10..18),
3425            ]
3426        );
3427
3428        let (message_6, message_7) =
3429            context.update(cx, |context, cx| context.split_message(14..16, cx));
3430        let message_6 = message_6.unwrap();
3431        let message_7 = message_7.unwrap();
3432        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\ndd\nd\n");
3433        assert_eq!(
3434            messages(&context, cx),
3435            vec![
3436                (message_1.id, Role::User, 0..4),
3437                (message_3.id, Role::User, 4..5),
3438                (message_2.id, Role::User, 5..9),
3439                (message_4.id, Role::User, 9..10),
3440                (message_5.id, Role::User, 10..14),
3441                (message_6.id, Role::User, 14..17),
3442                (message_7.id, Role::User, 17..19),
3443            ]
3444        );
3445    }
3446
3447    #[gpui::test]
3448    fn test_messages_for_offsets(cx: &mut AppContext) {
3449        let settings_store = SettingsStore::test(cx);
3450        cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
3451        cx.set_global(settings_store);
3452        init(cx);
3453        let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
3454        let context = cx.new_model(|cx| Context::new(registry, Default::default(), None, cx));
3455        let buffer = context.read(cx).buffer.clone();
3456
3457        let message_1 = context.read(cx).message_anchors[0].clone();
3458        assert_eq!(
3459            messages(&context, cx),
3460            vec![(message_1.id, Role::User, 0..0)]
3461        );
3462
3463        buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "aaa")], None, cx));
3464        let message_2 = context
3465            .update(cx, |context, cx| {
3466                context.insert_message_after(message_1.id, Role::User, MessageStatus::Done, cx)
3467            })
3468            .unwrap();
3469        buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "bbb")], None, cx));
3470
3471        let message_3 = context
3472            .update(cx, |context, cx| {
3473                context.insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
3474            })
3475            .unwrap();
3476        buffer.update(cx, |buffer, cx| buffer.edit([(8..8, "ccc")], None, cx));
3477
3478        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc");
3479        assert_eq!(
3480            messages(&context, cx),
3481            vec![
3482                (message_1.id, Role::User, 0..4),
3483                (message_2.id, Role::User, 4..8),
3484                (message_3.id, Role::User, 8..11)
3485            ]
3486        );
3487
3488        assert_eq!(
3489            message_ids_for_offsets(&context, &[0, 4, 9], cx),
3490            [message_1.id, message_2.id, message_3.id]
3491        );
3492        assert_eq!(
3493            message_ids_for_offsets(&context, &[0, 1, 11], cx),
3494            [message_1.id, message_3.id]
3495        );
3496
3497        let message_4 = context
3498            .update(cx, |context, cx| {
3499                context.insert_message_after(message_3.id, Role::User, MessageStatus::Done, cx)
3500            })
3501            .unwrap();
3502        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\n");
3503        assert_eq!(
3504            messages(&context, cx),
3505            vec![
3506                (message_1.id, Role::User, 0..4),
3507                (message_2.id, Role::User, 4..8),
3508                (message_3.id, Role::User, 8..12),
3509                (message_4.id, Role::User, 12..12)
3510            ]
3511        );
3512        assert_eq!(
3513            message_ids_for_offsets(&context, &[0, 4, 8, 12], cx),
3514            [message_1.id, message_2.id, message_3.id, message_4.id]
3515        );
3516
3517        fn message_ids_for_offsets(
3518            context: &Model<Context>,
3519            offsets: &[usize],
3520            cx: &AppContext,
3521        ) -> Vec<MessageId> {
3522            context
3523                .read(cx)
3524                .messages_for_offsets(offsets.iter().copied(), cx)
3525                .into_iter()
3526                .map(|message| message.id)
3527                .collect()
3528        }
3529    }
3530
3531    #[gpui::test]
3532    async fn test_slash_commands(cx: &mut TestAppContext) {
3533        let settings_store = cx.update(SettingsStore::test);
3534        cx.set_global(settings_store);
3535        cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
3536        cx.update(Project::init_settings);
3537        cx.update(init);
3538        let fs = FakeFs::new(cx.background_executor.clone());
3539
3540        fs.insert_tree(
3541            "/test",
3542            json!({
3543                "src": {
3544                    "lib.rs": "fn one() -> usize { 1 }",
3545                    "main.rs": "
3546                        use crate::one;
3547                        fn main() { one(); }
3548                    ".unindent(),
3549                }
3550            }),
3551        )
3552        .await;
3553
3554        let slash_command_registry = SlashCommandRegistry::new();
3555        slash_command_registry.register_command(file_command::FileSlashCommand, false);
3556        slash_command_registry.register_command(active_command::ActiveSlashCommand, false);
3557
3558        let registry = Arc::new(LanguageRegistry::test(cx.executor()));
3559        let context =
3560            cx.new_model(|cx| Context::new(registry.clone(), slash_command_registry, None, cx));
3561
3562        let output_ranges = Rc::new(RefCell::new(HashSet::default()));
3563        context.update(cx, |_, cx| {
3564            cx.subscribe(&context, {
3565                let ranges = output_ranges.clone();
3566                move |_, _, event, _| match event {
3567                    ContextEvent::PendingSlashCommandsUpdated { removed, updated } => {
3568                        for range in removed {
3569                            ranges.borrow_mut().remove(range);
3570                        }
3571                        for command in updated {
3572                            ranges.borrow_mut().insert(command.source_range.clone());
3573                        }
3574                    }
3575                    _ => {}
3576                }
3577            })
3578            .detach();
3579        });
3580
3581        let buffer = context.read_with(cx, |context, _| context.buffer.clone());
3582
3583        // Insert a slash command
3584        buffer.update(cx, |buffer, cx| {
3585            buffer.edit([(0..0, "/file src/lib.rs")], None, cx);
3586        });
3587        assert_text_and_output_ranges(
3588            &buffer,
3589            &output_ranges.borrow(),
3590            "
3591            «/file src/lib.rs»
3592            "
3593            .unindent()
3594            .trim_end(),
3595            cx,
3596        );
3597
3598        // Edit the argument of the slash command.
3599        buffer.update(cx, |buffer, cx| {
3600            let edit_offset = buffer.text().find("lib.rs").unwrap();
3601            buffer.edit([(edit_offset..edit_offset + "lib".len(), "main")], None, cx);
3602        });
3603        assert_text_and_output_ranges(
3604            &buffer,
3605            &output_ranges.borrow(),
3606            "
3607            «/file src/main.rs»
3608            "
3609            .unindent()
3610            .trim_end(),
3611            cx,
3612        );
3613
3614        // Edit the name of the slash command, using one that doesn't exist.
3615        buffer.update(cx, |buffer, cx| {
3616            let edit_offset = buffer.text().find("/file").unwrap();
3617            buffer.edit(
3618                [(edit_offset..edit_offset + "/file".len(), "/unknown")],
3619                None,
3620                cx,
3621            );
3622        });
3623        assert_text_and_output_ranges(
3624            &buffer,
3625            &output_ranges.borrow(),
3626            "
3627            /unknown src/main.rs
3628            "
3629            .unindent()
3630            .trim_end(),
3631            cx,
3632        );
3633
3634        #[track_caller]
3635        fn assert_text_and_output_ranges(
3636            buffer: &Model<Buffer>,
3637            ranges: &HashSet<Range<language::Anchor>>,
3638            expected_marked_text: &str,
3639            cx: &mut TestAppContext,
3640        ) {
3641            let (expected_text, expected_ranges) = marked_text_ranges(expected_marked_text, false);
3642            let (actual_text, actual_ranges) = buffer.update(cx, |buffer, _| {
3643                let mut ranges = ranges
3644                    .iter()
3645                    .map(|range| range.to_offset(buffer))
3646                    .collect::<Vec<_>>();
3647                ranges.sort_by_key(|a| a.start);
3648                (buffer.text(), ranges)
3649            });
3650
3651            assert_eq!(actual_text, expected_text);
3652            assert_eq!(actual_ranges, expected_ranges);
3653        }
3654    }
3655
3656    #[test]
3657    fn test_parse_next_edit_suggestion() {
3658        let text = "
3659            some output:
3660
3661            ```edit src/foo.rs
3662                let a = 1;
3663                let b = 2;
3664            ---
3665                let w = 1;
3666                let x = 2;
3667                let y = 3;
3668                let z = 4;
3669            ```
3670
3671            some more output:
3672
3673            ```edit src/foo.rs
3674                let c = 1;
3675            ---
3676            ```
3677
3678            and the conclusion.
3679        "
3680        .unindent();
3681
3682        let rope = Rope::from(text.as_str());
3683        let mut lines = rope.chunks().lines();
3684        let mut suggestions = vec![];
3685        while let Some(suggestion) = parse_next_edit_suggestion(&mut lines) {
3686            suggestions.push((
3687                suggestion.path.clone(),
3688                text[suggestion.old_text_range].to_string(),
3689                text[suggestion.new_text_range].to_string(),
3690            ));
3691        }
3692
3693        assert_eq!(
3694            suggestions,
3695            vec![
3696                (
3697                    Path::new("src/foo.rs").into(),
3698                    [
3699                        "    let a = 1;", //
3700                        "    let b = 2;",
3701                        "",
3702                    ]
3703                    .join("\n"),
3704                    [
3705                        "    let w = 1;",
3706                        "    let x = 2;",
3707                        "    let y = 3;",
3708                        "    let z = 4;",
3709                        "",
3710                    ]
3711                    .join("\n"),
3712                ),
3713                (
3714                    Path::new("src/foo.rs").into(),
3715                    [
3716                        "    let c = 1;", //
3717                        "",
3718                    ]
3719                    .join("\n"),
3720                    String::new(),
3721                )
3722            ]
3723        );
3724    }
3725
3726    #[gpui::test]
3727    async fn test_serialization(cx: &mut TestAppContext) {
3728        let settings_store = cx.update(SettingsStore::test);
3729        cx.set_global(settings_store);
3730        cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
3731        cx.update(init);
3732        let registry = Arc::new(LanguageRegistry::test(cx.executor()));
3733        let context =
3734            cx.new_model(|cx| Context::new(registry.clone(), Default::default(), None, cx));
3735        let buffer = context.read_with(cx, |context, _| context.buffer.clone());
3736        let message_0 = context.read_with(cx, |context, _| context.message_anchors[0].id);
3737        let message_1 = context.update(cx, |context, cx| {
3738            context
3739                .insert_message_after(message_0, Role::Assistant, MessageStatus::Done, cx)
3740                .unwrap()
3741        });
3742        let message_2 = context.update(cx, |context, cx| {
3743            context
3744                .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
3745                .unwrap()
3746        });
3747        buffer.update(cx, |buffer, cx| {
3748            buffer.edit([(0..0, "a"), (1..1, "b\nc")], None, cx);
3749            buffer.finalize_last_transaction();
3750        });
3751        let _message_3 = context.update(cx, |context, cx| {
3752            context
3753                .insert_message_after(message_2.id, Role::System, MessageStatus::Done, cx)
3754                .unwrap()
3755        });
3756        buffer.update(cx, |buffer, cx| buffer.undo(cx));
3757        assert_eq!(buffer.read_with(cx, |buffer, _| buffer.text()), "a\nb\nc\n");
3758        assert_eq!(
3759            cx.read(|cx| messages(&context, cx)),
3760            [
3761                (message_0, Role::User, 0..2),
3762                (message_1.id, Role::Assistant, 2..6),
3763                (message_2.id, Role::System, 6..6),
3764            ]
3765        );
3766
3767        let deserialized_context = Context::deserialize(
3768            context.read_with(cx, |context, cx| context.serialize(cx)),
3769            Default::default(),
3770            registry.clone(),
3771            Default::default(),
3772            None,
3773            &mut cx.to_async(),
3774        )
3775        .await
3776        .unwrap();
3777        let deserialized_buffer =
3778            deserialized_context.read_with(cx, |context, _| context.buffer.clone());
3779        assert_eq!(
3780            deserialized_buffer.read_with(cx, |buffer, _| buffer.text()),
3781            "a\nb\nc\n"
3782        );
3783        assert_eq!(
3784            cx.read(|cx| messages(&deserialized_context, cx)),
3785            [
3786                (message_0, Role::User, 0..2),
3787                (message_1.id, Role::Assistant, 2..6),
3788                (message_2.id, Role::System, 6..6),
3789            ]
3790        );
3791    }
3792
3793    fn messages(context: &Model<Context>, cx: &AppContext) -> Vec<(MessageId, Role, Range<usize>)> {
3794        context
3795            .read(cx)
3796            .messages(cx)
3797            .map(|message| (message.id, message.role, message.offset_range))
3798            .collect()
3799    }
3800}