assistant_panel.rs

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