assistant_panel.rs

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