assistant_panel.rs

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