assistant_panel.rs

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