assistant_panel.rs

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