assistant_panel.rs

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