assistant_panel.rs

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