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