assistant_panel.rs

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