assistant_panel.rs

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