assistant_panel.rs

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