assistant_panel.rs

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