assistant_panel.rs

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