assistant_panel.rs

   1use crate::ambient_context::{AmbientContext, ContextUpdated, RecentBuffer};
   2use crate::prompts::{generate_content_prompt, PromptLibrary, PromptManager};
   3use crate::{
   4    ambient_context::*,
   5    assistant_settings::{AssistantDockPosition, AssistantSettings, ZedDotDevModel},
   6    codegen::{self, Codegen, CodegenKind},
   7    omit_ranges::text_in_range_omitting_ranges,
   8    search::*,
   9    slash_command::{
  10        current_file_command, file_command, prompt_command, SlashCommandCleanup,
  11        SlashCommandCompletionProvider, SlashCommandLine, SlashCommandRegistry,
  12    },
  13    ApplyEdit, Assist, CompletionProvider, CycleMessageRole, InlineAssist, LanguageModel,
  14    LanguageModelRequest, LanguageModelRequestMessage, MessageId, MessageMetadata, MessageStatus,
  15    QuoteSelection, ResetKey, Role, SavedConversation, SavedConversationMetadata, SavedMessage,
  16    Split, ToggleFocus, ToggleHistory, ToggleIncludeConversation,
  17};
  18use anyhow::{anyhow, Result};
  19use client::telemetry::Telemetry;
  20use collections::{hash_map, HashMap, HashSet, VecDeque};
  21use editor::FoldPlaceholder;
  22use editor::{
  23    actions::{FoldAt, MoveDown, MoveUp},
  24    display_map::{
  25        BlockContext, BlockDisposition, BlockId, BlockProperties, BlockStyle, Flap, FlapId,
  26        ToDisplayPoint,
  27    },
  28    scroll::{Autoscroll, AutoscrollStrategy},
  29    Anchor, Editor, EditorElement, EditorEvent, EditorStyle, MultiBufferSnapshot, RowExt,
  30    ToOffset as _, ToPoint,
  31};
  32use file_icons::FileIcons;
  33use fs::Fs;
  34use futures::StreamExt;
  35use gpui::{
  36    canvas, div, point, relative, rems, uniform_list, Action, AnyElement, AnyView, AppContext,
  37    AsyncAppContext, AsyncWindowContext, AvailableSpace, ClipboardItem, Context, Empty, Entity,
  38    EventEmitter, FocusHandle, FocusableView, FontStyle, FontWeight, HighlightStyle,
  39    InteractiveElement, IntoElement, Model, ModelContext, ParentElement, Pixels, Render,
  40    SharedString, StatefulInteractiveElement, Styled, Subscription, Task, TextStyle,
  41    UniformListScrollHandle, View, ViewContext, VisualContext, WeakModel, WeakView, WhiteSpace,
  42    WindowContext,
  43};
  44use language::LspAdapterDelegate;
  45use language::{
  46    language_settings::SoftWrap, AutoindentMode, Buffer, BufferSnapshot, LanguageRegistry,
  47    OffsetRangeExt as _, Point, ToOffset as _, ToPoint as _,
  48};
  49use multi_buffer::MultiBufferRow;
  50use parking_lot::Mutex;
  51use project::{Project, ProjectLspAdapterDelegate, ProjectTransaction};
  52use search::{buffer_search::DivRegistrar, BufferSearchBar};
  53use settings::Settings;
  54use std::{
  55    cmp::{self, Ordering},
  56    fmt::Write,
  57    iter, mem,
  58    ops::Range,
  59    path::PathBuf,
  60    sync::Arc,
  61    time::{Duration, Instant},
  62};
  63use telemetry_events::AssistantKind;
  64use theme::ThemeSettings;
  65use ui::{
  66    popover_menu, prelude::*, ButtonLike, ContextMenu, ElevationIndex, KeyBinding, Tab, TabBar,
  67    Tooltip,
  68};
  69use util::{paths::CONVERSATIONS_DIR, post_inc, ResultExt, TryFutureExt};
  70use uuid::Uuid;
  71use workspace::{
  72    dock::{DockPosition, Panel, PanelEvent},
  73    searchable::Direction,
  74    Event as WorkspaceEvent, Save, Toast, ToggleZoom, Toolbar, Workspace,
  75};
  76use workspace::{notifications::NotificationId, NewFile};
  77
  78const MAX_RECENT_BUFFERS: usize = 3;
  79const SLASH_COMMAND_DEBOUNCE: Duration = Duration::from_millis(200);
  80
  81pub fn init(cx: &mut AppContext) {
  82    cx.observe_new_views(
  83        |workspace: &mut Workspace, _cx: &mut ViewContext<Workspace>| {
  84            workspace
  85                .register_action(|workspace, _: &ToggleFocus, cx| {
  86                    let settings = AssistantSettings::get_global(cx);
  87                    if !settings.enabled {
  88                        return;
  89                    }
  90
  91                    workspace.toggle_panel_focus::<AssistantPanel>(cx);
  92                })
  93                .register_action(AssistantPanel::inline_assist)
  94                .register_action(AssistantPanel::cancel_last_inline_assist)
  95                // .register_action(ConversationEditor::insert_active_prompt)
  96                .register_action(ConversationEditor::quote_selection);
  97        },
  98    )
  99    .detach();
 100}
 101
 102pub struct AssistantPanel {
 103    workspace: WeakView<Workspace>,
 104    width: Option<Pixels>,
 105    height: Option<Pixels>,
 106    active_conversation_editor: Option<ActiveConversationEditor>,
 107    show_saved_conversations: bool,
 108    saved_conversations: Vec<SavedConversationMetadata>,
 109    saved_conversations_scroll_handle: UniformListScrollHandle,
 110    zoomed: bool,
 111    focus_handle: FocusHandle,
 112    toolbar: View<Toolbar>,
 113    languages: Arc<LanguageRegistry>,
 114    slash_commands: Arc<SlashCommandRegistry>,
 115    prompt_library: Arc<PromptLibrary>,
 116    fs: Arc<dyn Fs>,
 117    telemetry: Arc<Telemetry>,
 118    _subscriptions: Vec<Subscription>,
 119    next_inline_assist_id: usize,
 120    pending_inline_assists: HashMap<usize, PendingInlineAssist>,
 121    pending_inline_assist_ids_by_editor: HashMap<WeakView<Editor>, Vec<usize>>,
 122    include_conversation_in_next_inline_assist: bool,
 123    inline_prompt_history: VecDeque<String>,
 124    _watch_saved_conversations: Task<Result<()>>,
 125    model: LanguageModel,
 126    authentication_prompt: Option<AnyView>,
 127}
 128
 129struct ActiveConversationEditor {
 130    editor: View<ConversationEditor>,
 131    _subscriptions: Vec<Subscription>,
 132}
 133
 134impl AssistantPanel {
 135    const INLINE_PROMPT_HISTORY_MAX_LEN: usize = 20;
 136
 137    pub fn load(
 138        workspace: WeakView<Workspace>,
 139        cx: AsyncWindowContext,
 140    ) -> Task<Result<View<Self>>> {
 141        cx.spawn(|mut cx| async move {
 142            let fs = workspace.update(&mut cx, |workspace, _| workspace.app_state().fs.clone())?;
 143            let saved_conversations = SavedConversationMetadata::list(fs.clone())
 144                .await
 145                .log_err()
 146                .unwrap_or_default();
 147
 148            let prompt_library = Arc::new(
 149                PromptLibrary::load_index(fs.clone())
 150                    .await
 151                    .log_err()
 152                    .unwrap_or_default(),
 153            );
 154
 155            // TODO: deserialize state.
 156            let workspace_handle = workspace.clone();
 157            workspace.update(&mut cx, |workspace, cx| {
 158                cx.new_view::<Self>(|cx| {
 159                    const CONVERSATION_WATCH_DURATION: Duration = Duration::from_millis(100);
 160                    let _watch_saved_conversations = cx.spawn(move |this, mut cx| async move {
 161                        let mut events = fs
 162                            .watch(&CONVERSATIONS_DIR, CONVERSATION_WATCH_DURATION)
 163                            .await;
 164                        while events.next().await.is_some() {
 165                            let saved_conversations = SavedConversationMetadata::list(fs.clone())
 166                                .await
 167                                .log_err()
 168                                .unwrap_or_default();
 169                            this.update(&mut cx, |this, cx| {
 170                                this.saved_conversations = saved_conversations;
 171                                cx.notify();
 172                            })
 173                            .ok();
 174                        }
 175
 176                        anyhow::Ok(())
 177                    });
 178
 179                    let toolbar = cx.new_view(|cx| {
 180                        let mut toolbar = Toolbar::new();
 181                        toolbar.set_can_navigate(false, cx);
 182                        toolbar.add_item(cx.new_view(BufferSearchBar::new), cx);
 183                        toolbar
 184                    });
 185
 186                    let focus_handle = cx.focus_handle();
 187                    let subscriptions = vec![
 188                        cx.on_focus_in(&focus_handle, Self::focus_in),
 189                        cx.on_focus_out(&focus_handle, Self::focus_out),
 190                        cx.observe_global::<CompletionProvider>({
 191                            let mut prev_settings_version =
 192                                CompletionProvider::global(cx).settings_version();
 193                            move |this, cx| {
 194                                this.completion_provider_changed(prev_settings_version, cx);
 195                                prev_settings_version =
 196                                    CompletionProvider::global(cx).settings_version();
 197                            }
 198                        }),
 199                    ];
 200                    let model = CompletionProvider::global(cx).default_model();
 201
 202                    cx.observe_global::<FileIcons>(|_, cx| {
 203                        cx.notify();
 204                    })
 205                    .detach();
 206
 207                    let slash_command_registry = SlashCommandRegistry::global(cx);
 208                    let window = cx.window_handle().downcast::<Workspace>();
 209
 210                    slash_command_registry.register_command(file_command::FileSlashCommand::new(
 211                        workspace.project().clone(),
 212                    ));
 213                    slash_command_registry.register_command(
 214                        prompt_command::PromptSlashCommand::new(prompt_library.clone()),
 215                    );
 216                    if let Some(window) = window {
 217                        slash_command_registry.register_command(
 218                            current_file_command::CurrentFileSlashCommand::new(window),
 219                        );
 220                    }
 221
 222                    Self {
 223                        workspace: workspace_handle,
 224                        active_conversation_editor: None,
 225                        show_saved_conversations: false,
 226                        saved_conversations,
 227                        saved_conversations_scroll_handle: Default::default(),
 228                        zoomed: false,
 229                        focus_handle,
 230                        toolbar,
 231                        languages: workspace.app_state().languages.clone(),
 232                        slash_commands: slash_command_registry,
 233                        prompt_library,
 234                        fs: workspace.app_state().fs.clone(),
 235                        telemetry: workspace.client().telemetry().clone(),
 236                        width: None,
 237                        height: None,
 238                        _subscriptions: subscriptions,
 239                        next_inline_assist_id: 0,
 240                        pending_inline_assists: Default::default(),
 241                        pending_inline_assist_ids_by_editor: Default::default(),
 242                        include_conversation_in_next_inline_assist: false,
 243                        inline_prompt_history: Default::default(),
 244                        _watch_saved_conversations,
 245                        model,
 246                        authentication_prompt: None,
 247                    }
 248                })
 249            })
 250        })
 251    }
 252
 253    fn focus_in(&mut self, cx: &mut ViewContext<Self>) {
 254        self.toolbar
 255            .update(cx, |toolbar, cx| toolbar.focus_changed(true, cx));
 256        cx.notify();
 257        if self.focus_handle.is_focused(cx) {
 258            if let Some(editor) = self.active_conversation_editor() {
 259                cx.focus_view(editor);
 260            }
 261        }
 262    }
 263
 264    fn focus_out(&mut self, cx: &mut ViewContext<Self>) {
 265        self.toolbar
 266            .update(cx, |toolbar, cx| toolbar.focus_changed(false, cx));
 267        cx.notify();
 268    }
 269
 270    fn completion_provider_changed(
 271        &mut self,
 272        prev_settings_version: usize,
 273        cx: &mut ViewContext<Self>,
 274    ) {
 275        if self.is_authenticated(cx) {
 276            self.authentication_prompt = None;
 277
 278            let model = CompletionProvider::global(cx).default_model();
 279            self.set_model(model, cx);
 280
 281            if self.active_conversation_editor().is_none() {
 282                self.new_conversation(cx);
 283            }
 284        } else if self.authentication_prompt.is_none()
 285            || prev_settings_version != CompletionProvider::global(cx).settings_version()
 286        {
 287            self.authentication_prompt =
 288                Some(cx.update_global::<CompletionProvider, _>(|provider, cx| {
 289                    provider.authentication_prompt(cx)
 290                }));
 291        }
 292    }
 293
 294    pub fn inline_assist(
 295        workspace: &mut Workspace,
 296        _: &InlineAssist,
 297        cx: &mut ViewContext<Workspace>,
 298    ) {
 299        let settings = AssistantSettings::get_global(cx);
 300        if !settings.enabled {
 301            return;
 302        }
 303
 304        let Some(assistant) = workspace.panel::<AssistantPanel>(cx) else {
 305            return;
 306        };
 307
 308        let conversation_editor =
 309            assistant
 310                .read(cx)
 311                .active_conversation_editor()
 312                .and_then(|editor| {
 313                    let editor = &editor.read(cx).editor;
 314                    if editor.read(cx).is_focused(cx) {
 315                        Some(editor.clone())
 316                    } else {
 317                        None
 318                    }
 319                });
 320
 321        let show_include_conversation;
 322        let active_editor;
 323        if let Some(conversation_editor) = conversation_editor {
 324            active_editor = conversation_editor;
 325            show_include_conversation = false;
 326        } else if let Some(workspace_editor) = workspace
 327            .active_item(cx)
 328            .and_then(|item| item.act_as::<Editor>(cx))
 329        {
 330            active_editor = workspace_editor;
 331            show_include_conversation = true;
 332        } else {
 333            return;
 334        };
 335        let project = workspace.project().clone();
 336
 337        if assistant.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
 338            assistant.update(cx, |assistant, cx| {
 339                assistant.new_inline_assist(&active_editor, &project, show_include_conversation, cx)
 340            });
 341        } else {
 342            let assistant = assistant.downgrade();
 343            cx.spawn(|workspace, mut cx| async move {
 344                assistant
 345                    .update(&mut cx, |assistant, cx| assistant.authenticate(cx))?
 346                    .await?;
 347                if assistant.update(&mut cx, |assistant, cx| assistant.is_authenticated(cx))? {
 348                    assistant.update(&mut cx, |assistant, cx| {
 349                        assistant.new_inline_assist(
 350                            &active_editor,
 351                            &project,
 352                            show_include_conversation,
 353                            cx,
 354                        )
 355                    })?;
 356                } else {
 357                    workspace.update(&mut cx, |workspace, cx| {
 358                        workspace.focus_panel::<AssistantPanel>(cx)
 359                    })?;
 360                }
 361
 362                anyhow::Ok(())
 363            })
 364            .detach_and_log_err(cx)
 365        }
 366    }
 367
 368    fn new_inline_assist(
 369        &mut self,
 370        editor: &View<Editor>,
 371        project: &Model<Project>,
 372        show_include_conversation: bool,
 373        cx: &mut ViewContext<Self>,
 374    ) {
 375        let selection = editor.read(cx).selections.newest_anchor().clone();
 376        if selection.start.excerpt_id != selection.end.excerpt_id {
 377            return;
 378        }
 379        let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
 380
 381        // Extend the selection to the start and the end of the line.
 382        let mut point_selection = selection.map(|selection| selection.to_point(&snapshot));
 383        if point_selection.end > point_selection.start {
 384            point_selection.start.column = 0;
 385            // If the selection ends at the start of the line, we don't want to include it.
 386            if point_selection.end.column == 0 {
 387                point_selection.end.row -= 1;
 388            }
 389            point_selection.end.column = snapshot.line_len(MultiBufferRow(point_selection.end.row));
 390        }
 391
 392        let codegen_kind = if point_selection.start == point_selection.end {
 393            CodegenKind::Generate {
 394                position: snapshot.anchor_after(point_selection.start),
 395            }
 396        } else {
 397            CodegenKind::Transform {
 398                range: snapshot.anchor_before(point_selection.start)
 399                    ..snapshot.anchor_after(point_selection.end),
 400            }
 401        };
 402
 403        let inline_assist_id = post_inc(&mut self.next_inline_assist_id);
 404        let telemetry = self.telemetry.clone();
 405
 406        let codegen = cx.new_model(|cx| {
 407            Codegen::new(
 408                editor.read(cx).buffer().clone(),
 409                codegen_kind,
 410                Some(telemetry),
 411                cx,
 412            )
 413        });
 414
 415        let measurements = Arc::new(Mutex::new(BlockMeasurements::default()));
 416        let inline_assistant = cx.new_view(|cx| {
 417            InlineAssistant::new(
 418                inline_assist_id,
 419                measurements.clone(),
 420                show_include_conversation,
 421                show_include_conversation && self.include_conversation_in_next_inline_assist,
 422                self.inline_prompt_history.clone(),
 423                codegen.clone(),
 424                cx,
 425            )
 426        });
 427        let block_id = editor.update(cx, |editor, cx| {
 428            editor.change_selections(None, cx, |selections| {
 429                selections.select_anchor_ranges([selection.head()..selection.head()])
 430            });
 431            editor.insert_blocks(
 432                [BlockProperties {
 433                    style: BlockStyle::Flex,
 434                    position: snapshot.anchor_before(Point::new(point_selection.head().row, 0)),
 435                    height: 2,
 436                    render: Box::new({
 437                        let inline_assistant = inline_assistant.clone();
 438                        move |cx: &mut BlockContext| {
 439                            *measurements.lock() = BlockMeasurements {
 440                                anchor_x: cx.anchor_x,
 441                                gutter_width: cx.gutter_dimensions.width,
 442                            };
 443                            inline_assistant.clone().into_any_element()
 444                        }
 445                    }),
 446                    disposition: if selection.reversed {
 447                        BlockDisposition::Above
 448                    } else {
 449                        BlockDisposition::Below
 450                    },
 451                }],
 452                Some(Autoscroll::Strategy(AutoscrollStrategy::Newest)),
 453                cx,
 454            )[0]
 455        });
 456
 457        self.pending_inline_assists.insert(
 458            inline_assist_id,
 459            PendingInlineAssist {
 460                editor: editor.downgrade(),
 461                inline_assistant: Some((block_id, inline_assistant.clone())),
 462                codegen: codegen.clone(),
 463                project: project.downgrade(),
 464                _subscriptions: vec![
 465                    cx.subscribe(&inline_assistant, Self::handle_inline_assistant_event),
 466                    cx.subscribe(editor, {
 467                        let inline_assistant = inline_assistant.downgrade();
 468                        move |_, editor, event, cx| {
 469                            if let Some(inline_assistant) = inline_assistant.upgrade() {
 470                                if let EditorEvent::SelectionsChanged { local } = event {
 471                                    if *local
 472                                        && inline_assistant.focus_handle(cx).contains_focused(cx)
 473                                    {
 474                                        cx.focus_view(&editor);
 475                                    }
 476                                }
 477                            }
 478                        }
 479                    }),
 480                    cx.observe(&codegen, {
 481                        let editor = editor.downgrade();
 482                        move |this, _, cx| {
 483                            if let Some(editor) = editor.upgrade() {
 484                                this.update_highlights_for_editor(&editor, cx);
 485                            }
 486                        }
 487                    }),
 488                    cx.subscribe(&codegen, move |this, codegen, event, cx| match event {
 489                        codegen::Event::Undone => {
 490                            this.finish_inline_assist(inline_assist_id, false, cx)
 491                        }
 492                        codegen::Event::Finished => {
 493                            let pending_assist = if let Some(pending_assist) =
 494                                this.pending_inline_assists.get(&inline_assist_id)
 495                            {
 496                                pending_assist
 497                            } else {
 498                                return;
 499                            };
 500
 501                            let error = codegen
 502                                .read(cx)
 503                                .error()
 504                                .map(|error| format!("Inline assistant error: {}", error));
 505                            if let Some(error) = error {
 506                                if pending_assist.inline_assistant.is_none() {
 507                                    if let Some(workspace) = this.workspace.upgrade() {
 508                                        workspace.update(cx, |workspace, cx| {
 509                                            struct InlineAssistantError;
 510
 511                                            let id =
 512                                                NotificationId::identified::<InlineAssistantError>(
 513                                                    inline_assist_id,
 514                                                );
 515
 516                                            workspace.show_toast(Toast::new(id, error), cx);
 517                                        })
 518                                    }
 519
 520                                    this.finish_inline_assist(inline_assist_id, false, cx);
 521                                }
 522                            } else {
 523                                this.finish_inline_assist(inline_assist_id, false, cx);
 524                            }
 525                        }
 526                    }),
 527                ],
 528            },
 529        );
 530        self.pending_inline_assist_ids_by_editor
 531            .entry(editor.downgrade())
 532            .or_default()
 533            .push(inline_assist_id);
 534        self.update_highlights_for_editor(editor, cx);
 535    }
 536
 537    fn handle_inline_assistant_event(
 538        &mut self,
 539        inline_assistant: View<InlineAssistant>,
 540        event: &InlineAssistantEvent,
 541        cx: &mut ViewContext<Self>,
 542    ) {
 543        let assist_id = inline_assistant.read(cx).id;
 544        match event {
 545            InlineAssistantEvent::Confirmed {
 546                prompt,
 547                include_conversation,
 548            } => {
 549                self.confirm_inline_assist(assist_id, prompt, *include_conversation, cx);
 550            }
 551            InlineAssistantEvent::Canceled => {
 552                self.finish_inline_assist(assist_id, true, cx);
 553            }
 554            InlineAssistantEvent::Dismissed => {
 555                self.hide_inline_assist(assist_id, cx);
 556            }
 557            InlineAssistantEvent::IncludeConversationToggled {
 558                include_conversation,
 559            } => {
 560                self.include_conversation_in_next_inline_assist = *include_conversation;
 561            }
 562        }
 563    }
 564
 565    fn cancel_last_inline_assist(
 566        workspace: &mut Workspace,
 567        _: &editor::actions::Cancel,
 568        cx: &mut ViewContext<Workspace>,
 569    ) {
 570        if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 571            if let Some(editor) = workspace
 572                .active_item(cx)
 573                .and_then(|item| item.downcast::<Editor>())
 574            {
 575                let handled = panel.update(cx, |panel, cx| {
 576                    if let Some(assist_id) = panel
 577                        .pending_inline_assist_ids_by_editor
 578                        .get(&editor.downgrade())
 579                        .and_then(|assist_ids| assist_ids.last().copied())
 580                    {
 581                        panel.finish_inline_assist(assist_id, true, cx);
 582                        true
 583                    } else {
 584                        false
 585                    }
 586                });
 587                if handled {
 588                    return;
 589                }
 590            }
 591        }
 592
 593        cx.propagate();
 594    }
 595
 596    fn finish_inline_assist(&mut self, assist_id: usize, undo: bool, cx: &mut ViewContext<Self>) {
 597        self.hide_inline_assist(assist_id, cx);
 598
 599        if let Some(pending_assist) = self.pending_inline_assists.remove(&assist_id) {
 600            if let hash_map::Entry::Occupied(mut entry) = self
 601                .pending_inline_assist_ids_by_editor
 602                .entry(pending_assist.editor.clone())
 603            {
 604                entry.get_mut().retain(|id| *id != assist_id);
 605                if entry.get().is_empty() {
 606                    entry.remove();
 607                }
 608            }
 609
 610            if let Some(editor) = pending_assist.editor.upgrade() {
 611                self.update_highlights_for_editor(&editor, cx);
 612
 613                if undo {
 614                    pending_assist
 615                        .codegen
 616                        .update(cx, |codegen, cx| codegen.undo(cx));
 617                }
 618            }
 619        }
 620    }
 621
 622    fn hide_inline_assist(&mut self, assist_id: usize, cx: &mut ViewContext<Self>) {
 623        if let Some(pending_assist) = self.pending_inline_assists.get_mut(&assist_id) {
 624            if let Some(editor) = pending_assist.editor.upgrade() {
 625                if let Some((block_id, inline_assistant)) = pending_assist.inline_assistant.take() {
 626                    editor.update(cx, |editor, cx| {
 627                        editor.remove_blocks(HashSet::from_iter([block_id]), None, cx);
 628                        if inline_assistant.focus_handle(cx).contains_focused(cx) {
 629                            editor.focus(cx);
 630                        }
 631                    });
 632                }
 633            }
 634        }
 635    }
 636
 637    fn confirm_inline_assist(
 638        &mut self,
 639        inline_assist_id: usize,
 640        user_prompt: &str,
 641        include_conversation: bool,
 642        cx: &mut ViewContext<Self>,
 643    ) {
 644        let conversation = if include_conversation {
 645            self.active_conversation_editor()
 646                .map(|editor| editor.read(cx).conversation.clone())
 647        } else {
 648            None
 649        };
 650
 651        let pending_assist =
 652            if let Some(pending_assist) = self.pending_inline_assists.get_mut(&inline_assist_id) {
 653                pending_assist
 654            } else {
 655                return;
 656            };
 657
 658        let editor = if let Some(editor) = pending_assist.editor.upgrade() {
 659            editor
 660        } else {
 661            return;
 662        };
 663
 664        let project = pending_assist.project.clone();
 665
 666        let project_name = project.upgrade().map(|project| {
 667            project
 668                .read(cx)
 669                .worktree_root_names(cx)
 670                .collect::<Vec<&str>>()
 671                .join("/")
 672        });
 673
 674        self.inline_prompt_history
 675            .retain(|prompt| prompt != user_prompt);
 676        self.inline_prompt_history.push_back(user_prompt.into());
 677        if self.inline_prompt_history.len() > Self::INLINE_PROMPT_HISTORY_MAX_LEN {
 678            self.inline_prompt_history.pop_front();
 679        }
 680
 681        let codegen = pending_assist.codegen.clone();
 682        let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
 683        let range = codegen.read(cx).range();
 684        let start = snapshot.point_to_buffer_offset(range.start);
 685        let end = snapshot.point_to_buffer_offset(range.end);
 686        let (buffer, range) = if let Some((start, end)) = start.zip(end) {
 687            let (start_buffer, start_buffer_offset) = start;
 688            let (end_buffer, end_buffer_offset) = end;
 689            if start_buffer.remote_id() == end_buffer.remote_id() {
 690                (start_buffer.clone(), start_buffer_offset..end_buffer_offset)
 691            } else {
 692                self.finish_inline_assist(inline_assist_id, false, cx);
 693                return;
 694            }
 695        } else {
 696            self.finish_inline_assist(inline_assist_id, false, cx);
 697            return;
 698        };
 699
 700        let language = buffer.language_at(range.start);
 701        let language_name = if let Some(language) = language.as_ref() {
 702            if Arc::ptr_eq(language, &language::PLAIN_TEXT) {
 703                None
 704            } else {
 705                Some(language.name())
 706            }
 707        } else {
 708            None
 709        };
 710
 711        // Higher Temperature increases the randomness of model outputs.
 712        // If Markdown or No Language is Known, increase the randomness for more creative output
 713        // If Code, decrease temperature to get more deterministic outputs
 714        let temperature = if let Some(language) = language_name.clone() {
 715            if language.as_ref() == "Markdown" {
 716                1.0
 717            } else {
 718                0.5
 719            }
 720        } else {
 721            1.0
 722        };
 723
 724        let user_prompt = user_prompt.to_string();
 725
 726        let prompt = cx.background_executor().spawn(async move {
 727            let language_name = language_name.as_deref();
 728            generate_content_prompt(user_prompt, language_name, buffer, range, project_name)
 729        });
 730
 731        let mut messages = Vec::new();
 732        if let Some(conversation) = conversation {
 733            let conversation = conversation.read(cx);
 734            let buffer = conversation.buffer.read(cx);
 735            messages.extend(
 736                conversation
 737                    .messages(cx)
 738                    .map(|message| message.to_request_message(buffer)),
 739            );
 740        }
 741        let model = self.model.clone();
 742
 743        cx.spawn(|_, mut cx| async move {
 744            // I Don't know if we want to return a ? here.
 745            let prompt = prompt.await?;
 746
 747            messages.push(LanguageModelRequestMessage {
 748                role: Role::User,
 749                content: prompt,
 750            });
 751
 752            let request = LanguageModelRequest {
 753                model,
 754                messages,
 755                stop: vec!["|END|>".to_string()],
 756                temperature,
 757            };
 758
 759            codegen.update(&mut cx, |codegen, cx| codegen.start(request, cx))?;
 760            anyhow::Ok(())
 761        })
 762        .detach();
 763    }
 764
 765    fn update_highlights_for_editor(&self, editor: &View<Editor>, cx: &mut ViewContext<Self>) {
 766        let mut background_ranges = Vec::new();
 767        let mut foreground_ranges = Vec::new();
 768        let empty_inline_assist_ids = Vec::new();
 769        let inline_assist_ids = self
 770            .pending_inline_assist_ids_by_editor
 771            .get(&editor.downgrade())
 772            .unwrap_or(&empty_inline_assist_ids);
 773
 774        for inline_assist_id in inline_assist_ids {
 775            if let Some(pending_assist) = self.pending_inline_assists.get(inline_assist_id) {
 776                let codegen = pending_assist.codegen.read(cx);
 777                background_ranges.push(codegen.range());
 778                foreground_ranges.extend(codegen.last_equal_ranges().iter().cloned());
 779            }
 780        }
 781
 782        let snapshot = editor.read(cx).buffer().read(cx).snapshot(cx);
 783        merge_ranges(&mut background_ranges, &snapshot);
 784        merge_ranges(&mut foreground_ranges, &snapshot);
 785        editor.update(cx, |editor, cx| {
 786            if background_ranges.is_empty() {
 787                editor.clear_background_highlights::<PendingInlineAssist>(cx);
 788            } else {
 789                editor.highlight_background::<PendingInlineAssist>(
 790                    &background_ranges,
 791                    |theme| theme.editor_active_line_background, // TODO use the appropriate color
 792                    cx,
 793                );
 794            }
 795
 796            if foreground_ranges.is_empty() {
 797                editor.clear_highlights::<PendingInlineAssist>(cx);
 798            } else {
 799                editor.highlight_text::<PendingInlineAssist>(
 800                    foreground_ranges,
 801                    HighlightStyle {
 802                        fade_out: Some(0.6),
 803                        ..Default::default()
 804                    },
 805                    cx,
 806                );
 807            }
 808        });
 809    }
 810
 811    fn new_conversation(&mut self, cx: &mut ViewContext<Self>) -> Option<View<ConversationEditor>> {
 812        let workspace = self.workspace.upgrade()?;
 813
 814        let editor = cx.new_view(|cx| {
 815            ConversationEditor::new(
 816                self.model.clone(),
 817                self.languages.clone(),
 818                self.slash_commands.clone(),
 819                self.fs.clone(),
 820                workspace,
 821                cx,
 822            )
 823        });
 824
 825        self.show_conversation(editor.clone(), cx);
 826        Some(editor)
 827    }
 828
 829    fn show_conversation(
 830        &mut self,
 831        conversation_editor: View<ConversationEditor>,
 832        cx: &mut ViewContext<Self>,
 833    ) {
 834        let mut subscriptions = Vec::new();
 835        subscriptions
 836            .push(cx.subscribe(&conversation_editor, Self::handle_conversation_editor_event));
 837
 838        let conversation = conversation_editor.read(cx).conversation.clone();
 839        subscriptions.push(cx.observe(&conversation, |_, _, cx| cx.notify()));
 840
 841        let editor = conversation_editor.read(cx).editor.clone();
 842        self.toolbar.update(cx, |toolbar, cx| {
 843            toolbar.set_active_item(Some(&editor), cx);
 844        });
 845        if self.focus_handle.contains_focused(cx) {
 846            cx.focus_view(&editor);
 847        }
 848        self.active_conversation_editor = Some(ActiveConversationEditor {
 849            editor: conversation_editor,
 850            _subscriptions: subscriptions,
 851        });
 852        self.show_saved_conversations = false;
 853
 854        cx.notify();
 855    }
 856
 857    fn cycle_model(&mut self, cx: &mut ViewContext<Self>) {
 858        let next_model = match &self.model {
 859            LanguageModel::OpenAi(model) => LanguageModel::OpenAi(match &model {
 860                open_ai::Model::ThreePointFiveTurbo => open_ai::Model::Four,
 861                open_ai::Model::Four => open_ai::Model::FourTurbo,
 862                open_ai::Model::FourTurbo => open_ai::Model::FourOmni,
 863                open_ai::Model::FourOmni => open_ai::Model::ThreePointFiveTurbo,
 864            }),
 865            LanguageModel::Anthropic(model) => LanguageModel::Anthropic(match &model {
 866                anthropic::Model::Claude3Opus => anthropic::Model::Claude3Sonnet,
 867                anthropic::Model::Claude3Sonnet => anthropic::Model::Claude3Haiku,
 868                anthropic::Model::Claude3Haiku => anthropic::Model::Claude3Opus,
 869            }),
 870            LanguageModel::ZedDotDev(model) => LanguageModel::ZedDotDev(match &model {
 871                ZedDotDevModel::Gpt3Point5Turbo => ZedDotDevModel::Gpt4,
 872                ZedDotDevModel::Gpt4 => ZedDotDevModel::Gpt4Turbo,
 873                ZedDotDevModel::Gpt4Turbo => ZedDotDevModel::Gpt4Omni,
 874                ZedDotDevModel::Gpt4Omni => ZedDotDevModel::Claude3Opus,
 875                ZedDotDevModel::Claude3Opus => ZedDotDevModel::Claude3Sonnet,
 876                ZedDotDevModel::Claude3Sonnet => ZedDotDevModel::Claude3Haiku,
 877                ZedDotDevModel::Claude3Haiku => {
 878                    match CompletionProvider::global(cx).default_model() {
 879                        LanguageModel::ZedDotDev(custom @ ZedDotDevModel::Custom(_)) => custom,
 880                        _ => ZedDotDevModel::Gpt3Point5Turbo,
 881                    }
 882                }
 883                ZedDotDevModel::Custom(_) => ZedDotDevModel::Gpt3Point5Turbo,
 884            }),
 885        };
 886
 887        self.set_model(next_model, cx);
 888    }
 889
 890    fn set_model(&mut self, model: LanguageModel, cx: &mut ViewContext<Self>) {
 891        self.model = model.clone();
 892        if let Some(editor) = self.active_conversation_editor() {
 893            editor.update(cx, |active_conversation, cx| {
 894                active_conversation
 895                    .conversation
 896                    .update(cx, |conversation, cx| {
 897                        conversation.set_model(model, cx);
 898                    })
 899            })
 900        }
 901        cx.notify();
 902    }
 903
 904    fn handle_conversation_editor_event(
 905        &mut self,
 906        _: View<ConversationEditor>,
 907        event: &ConversationEditorEvent,
 908        cx: &mut ViewContext<Self>,
 909    ) {
 910        match event {
 911            ConversationEditorEvent::TabContentChanged => cx.notify(),
 912        }
 913    }
 914
 915    fn toggle_zoom(&mut self, _: &workspace::ToggleZoom, cx: &mut ViewContext<Self>) {
 916        if self.zoomed {
 917            cx.emit(PanelEvent::ZoomOut)
 918        } else {
 919            cx.emit(PanelEvent::ZoomIn)
 920        }
 921    }
 922
 923    fn toggle_history(&mut self, _: &ToggleHistory, cx: &mut ViewContext<Self>) {
 924        self.show_saved_conversations = !self.show_saved_conversations;
 925        cx.notify();
 926    }
 927
 928    fn show_history(&mut self, cx: &mut ViewContext<Self>) {
 929        if !self.show_saved_conversations {
 930            self.show_saved_conversations = true;
 931            cx.notify();
 932        }
 933    }
 934
 935    fn deploy(&mut self, action: &search::buffer_search::Deploy, cx: &mut ViewContext<Self>) {
 936        let mut propagate = true;
 937        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
 938            search_bar.update(cx, |search_bar, cx| {
 939                if search_bar.show(cx) {
 940                    search_bar.search_suggested(cx);
 941                    if action.focus {
 942                        let focus_handle = search_bar.focus_handle(cx);
 943                        search_bar.select_query(cx);
 944                        cx.focus(&focus_handle);
 945                    }
 946                    propagate = false
 947                }
 948            });
 949        }
 950        if propagate {
 951            cx.propagate();
 952        }
 953    }
 954
 955    fn handle_editor_cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
 956        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
 957            if !search_bar.read(cx).is_dismissed() {
 958                search_bar.update(cx, |search_bar, cx| {
 959                    search_bar.dismiss(&Default::default(), cx)
 960                });
 961                return;
 962            }
 963        }
 964        cx.propagate();
 965    }
 966
 967    fn select_next_match(&mut self, _: &search::SelectNextMatch, cx: &mut ViewContext<Self>) {
 968        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
 969            search_bar.update(cx, |bar, cx| bar.select_match(Direction::Next, 1, cx));
 970        }
 971    }
 972
 973    fn select_prev_match(&mut self, _: &search::SelectPrevMatch, cx: &mut ViewContext<Self>) {
 974        if let Some(search_bar) = self.toolbar.read(cx).item_of_type::<BufferSearchBar>() {
 975            search_bar.update(cx, |bar, cx| bar.select_match(Direction::Prev, 1, cx));
 976        }
 977    }
 978
 979    fn reset_credentials(&mut self, _: &ResetKey, cx: &mut ViewContext<Self>) {
 980        CompletionProvider::global(cx)
 981            .reset_credentials(cx)
 982            .detach_and_log_err(cx);
 983    }
 984
 985    fn active_conversation_editor(&self) -> Option<&View<ConversationEditor>> {
 986        Some(&self.active_conversation_editor.as_ref()?.editor)
 987    }
 988
 989    fn render_popover_button(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
 990        let assistant = cx.view().clone();
 991        let zoomed = self.zoomed;
 992        popover_menu("assistant-popover")
 993            .trigger(IconButton::new("trigger", IconName::Menu))
 994            .menu(move |cx| {
 995                let assistant = assistant.clone();
 996                ContextMenu::build(cx, |menu, _cx| {
 997                    menu.entry(
 998                        if zoomed { "Zoom Out" } else { "Zoom In" },
 999                        Some(Box::new(ToggleZoom)),
1000                        {
1001                            let assistant = assistant.clone();
1002                            move |cx| {
1003                                assistant.focus_handle(cx).dispatch_action(&ToggleZoom, cx);
1004                            }
1005                        },
1006                    )
1007                    .entry("New Context", Some(Box::new(NewFile)), {
1008                        let assistant = assistant.clone();
1009                        move |cx| {
1010                            assistant.focus_handle(cx).dispatch_action(&NewFile, cx);
1011                        }
1012                    })
1013                    .entry("History", Some(Box::new(ToggleHistory)), {
1014                        let assistant = assistant.clone();
1015                        move |cx| assistant.update(cx, |assistant, cx| assistant.show_history(cx))
1016                    })
1017                })
1018                .into()
1019            })
1020    }
1021
1022    fn render_inject_context_menu(&self, _cx: &mut ViewContext<Self>) -> impl Element {
1023        let workspace = self.workspace.clone();
1024
1025        popover_menu("inject-context-menu")
1026            .trigger(IconButton::new("trigger", IconName::Quote).tooltip(|cx| {
1027                // Tooltip::with_meta("Insert Context", None, "Type # to insert via keyboard", cx)
1028                Tooltip::text("Insert Context", cx)
1029            }))
1030            .menu(move |cx| {
1031                ContextMenu::build(cx, |menu, _cx| {
1032                    // menu.entry("Insert Search", None, {
1033                    //     let assistant = assistant.clone();
1034                    //     move |_cx| {}
1035                    // })
1036                    // .entry("Insert Docs", None, {
1037                    //     let assistant = assistant.clone();
1038                    //     move |cx| {}
1039                    // })
1040                    menu.entry("Quote Selection", None, {
1041                        let workspace = workspace.clone();
1042                        move |cx| {
1043                            workspace
1044                                .update(cx, |workspace, cx| {
1045                                    ConversationEditor::quote_selection(
1046                                        workspace,
1047                                        &Default::default(),
1048                                        cx,
1049                                    )
1050                                })
1051                                .ok();
1052                        }
1053                    })
1054                    // .entry("Insert Active Prompt", None, {
1055                    //     let workspace = workspace.clone();
1056                    //     move |cx| {
1057                    //         workspace
1058                    //             .update(cx, |workspace, cx| {
1059                    //                 ConversationEditor::insert_active_prompt(
1060                    //                     workspace,
1061                    //                     &Default::default(),
1062                    //                     cx,
1063                    //                 )
1064                    //             })
1065                    //             .ok();
1066                    //     }
1067                    // })
1068                })
1069                .into()
1070            })
1071    }
1072
1073    fn render_send_button(&self, cx: &mut ViewContext<Self>) -> Option<impl IntoElement> {
1074        self.active_conversation_editor
1075            .as_ref()
1076            .map(|conversation| {
1077                let focus_handle = conversation.editor.focus_handle(cx);
1078                ButtonLike::new("send_button")
1079                    .style(ButtonStyle::Filled)
1080                    .layer(ElevationIndex::ModalSurface)
1081                    .children(
1082                        KeyBinding::for_action_in(&Assist, &focus_handle, cx)
1083                            .map(|binding| binding.into_any_element()),
1084                    )
1085                    .child(Label::new("Send"))
1086                    .on_click(cx.listener(|this, _event, cx| {
1087                        if let Some(active_editor) = this.active_conversation_editor() {
1088                            active_editor.update(cx, |editor, cx| editor.assist(&Assist, cx));
1089                        }
1090                    }))
1091            })
1092    }
1093
1094    fn render_saved_conversation(
1095        &mut self,
1096        index: usize,
1097        cx: &mut ViewContext<Self>,
1098    ) -> impl IntoElement {
1099        let conversation = &self.saved_conversations[index];
1100        let path = conversation.path.clone();
1101
1102        ButtonLike::new(index)
1103            .on_click(cx.listener(move |this, _, cx| {
1104                this.open_conversation(path.clone(), cx)
1105                    .detach_and_log_err(cx)
1106            }))
1107            .full_width()
1108            .child(
1109                div()
1110                    .flex()
1111                    .w_full()
1112                    .gap_2()
1113                    .child(
1114                        Label::new(conversation.mtime.format("%F %I:%M%p").to_string())
1115                            .color(Color::Muted)
1116                            .size(LabelSize::Small),
1117                    )
1118                    .child(Label::new(conversation.title.clone()).size(LabelSize::Small)),
1119            )
1120    }
1121
1122    fn open_conversation(&mut self, path: PathBuf, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
1123        cx.focus(&self.focus_handle);
1124
1125        let fs = self.fs.clone();
1126        let workspace = self.workspace.clone();
1127        let slash_commands = self.slash_commands.clone();
1128        let languages = self.languages.clone();
1129        let telemetry = self.telemetry.clone();
1130
1131        let lsp_adapter_delegate = workspace
1132            .update(cx, |workspace, cx| {
1133                make_lsp_adapter_delegate(workspace.project(), cx).log_err()
1134            })
1135            .log_err()
1136            .flatten();
1137
1138        cx.spawn(|this, mut cx| async move {
1139            let saved_conversation = SavedConversation::load(&path, fs.as_ref()).await?;
1140            let model = this.update(&mut cx, |this, _| this.model.clone())?;
1141            let conversation = Conversation::deserialize(
1142                saved_conversation,
1143                model,
1144                path.clone(),
1145                languages,
1146                slash_commands,
1147                Some(telemetry),
1148                lsp_adapter_delegate,
1149                &mut cx,
1150            )
1151            .await?;
1152
1153            this.update(&mut cx, |this, cx| {
1154                let workspace = workspace
1155                    .upgrade()
1156                    .ok_or_else(|| anyhow!("workspace dropped"))?;
1157                let editor = cx.new_view(|cx| {
1158                    ConversationEditor::for_conversation(conversation, fs, workspace, cx)
1159                });
1160                this.show_conversation(editor, cx);
1161                anyhow::Ok(())
1162            })??;
1163            Ok(())
1164        })
1165    }
1166
1167    fn show_prompt_manager(&mut self, cx: &mut ViewContext<Self>) {
1168        if let Some(workspace) = self.workspace.upgrade() {
1169            workspace.update(cx, |workspace, cx| {
1170                workspace.toggle_modal(cx, |cx| {
1171                    PromptManager::new(
1172                        self.prompt_library.clone(),
1173                        self.languages.clone(),
1174                        self.fs.clone(),
1175                        cx,
1176                    )
1177                })
1178            })
1179        }
1180    }
1181
1182    fn is_authenticated(&mut self, cx: &mut ViewContext<Self>) -> bool {
1183        CompletionProvider::global(cx).is_authenticated()
1184    }
1185
1186    fn authenticate(&mut self, cx: &mut ViewContext<Self>) -> Task<Result<()>> {
1187        cx.update_global::<CompletionProvider, _>(|provider, cx| provider.authenticate(cx))
1188    }
1189
1190    fn render_signed_in(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1191        let header =
1192            TabBar::new("assistant_header")
1193                .start_child(h_flex().gap_1().child(self.render_popover_button(cx)))
1194                .children(self.active_conversation_editor().map(|editor| {
1195                    h_flex()
1196                        .h(rems(Tab::CONTAINER_HEIGHT_IN_REMS))
1197                        .flex_1()
1198                        .px_2()
1199                        .child(Label::new(editor.read(cx).title(cx)).into_element())
1200                }))
1201                .end_child(
1202                    h_flex()
1203                        .gap_2()
1204                        .when_some(self.active_conversation_editor(), |this, editor| {
1205                            let conversation = editor.read(cx).conversation.clone();
1206                            this.child(
1207                                h_flex()
1208                                    .gap_1()
1209                                    .child(self.render_model(&conversation, cx))
1210                                    .children(self.render_remaining_tokens(&conversation, cx)),
1211                            )
1212                            .child(
1213                                ui::Divider::vertical()
1214                                    .inset()
1215                                    .color(ui::DividerColor::Border),
1216                            )
1217                        })
1218                        .child(
1219                            h_flex()
1220                                .gap_1()
1221                                .child(self.render_inject_context_menu(cx))
1222                                .child(
1223                                    IconButton::new("show_prompt_manager", IconName::Library)
1224                                        .icon_size(IconSize::Small)
1225                                        .on_click(cx.listener(|this, _event, cx| {
1226                                            this.show_prompt_manager(cx)
1227                                        }))
1228                                        .tooltip(|cx| Tooltip::text("Prompt Library…", cx)),
1229                                ),
1230                        ),
1231                );
1232
1233        let contents = if self.active_conversation_editor().is_some() {
1234            let mut registrar = DivRegistrar::new(
1235                |panel, cx| panel.toolbar.read(cx).item_of_type::<BufferSearchBar>(),
1236                cx,
1237            );
1238            BufferSearchBar::register(&mut registrar);
1239            registrar.into_div()
1240        } else {
1241            div()
1242        };
1243
1244        v_flex()
1245            .key_context("AssistantPanel")
1246            .size_full()
1247            .on_action(cx.listener(|this, _: &workspace::NewFile, cx| {
1248                this.new_conversation(cx);
1249            }))
1250            .on_action(cx.listener(AssistantPanel::toggle_zoom))
1251            .on_action(cx.listener(AssistantPanel::toggle_history))
1252            .on_action(cx.listener(AssistantPanel::deploy))
1253            .on_action(cx.listener(AssistantPanel::select_next_match))
1254            .on_action(cx.listener(AssistantPanel::select_prev_match))
1255            .on_action(cx.listener(AssistantPanel::handle_editor_cancel))
1256            .on_action(cx.listener(AssistantPanel::reset_credentials))
1257            .track_focus(&self.focus_handle)
1258            .child(header)
1259            .children(if self.toolbar.read(cx).hidden() {
1260                None
1261            } else {
1262                Some(self.toolbar.clone())
1263            })
1264            .child(contents.flex_1().child(
1265                if self.show_saved_conversations || self.active_conversation_editor().is_none() {
1266                    let view = cx.view().clone();
1267                    let scroll_handle = self.saved_conversations_scroll_handle.clone();
1268                    let conversation_count = self.saved_conversations.len();
1269                    canvas(
1270                        move |bounds, cx| {
1271                            let mut saved_conversations = uniform_list(
1272                                view,
1273                                "saved_conversations",
1274                                conversation_count,
1275                                |this, range, cx| {
1276                                    range
1277                                        .map(|ix| this.render_saved_conversation(ix, cx))
1278                                        .collect()
1279                                },
1280                            )
1281                            .track_scroll(scroll_handle)
1282                            .into_any_element();
1283                            saved_conversations.prepaint_as_root(
1284                                bounds.origin,
1285                                bounds.size.map(AvailableSpace::Definite),
1286                                cx,
1287                            );
1288                            saved_conversations
1289                        },
1290                        |_bounds, mut saved_conversations, cx| saved_conversations.paint(cx),
1291                    )
1292                    .size_full()
1293                    .into_any_element()
1294                } else if let Some(editor) = self.active_conversation_editor() {
1295                    let editor = editor.clone();
1296                    div()
1297                        .size_full()
1298                        .child(editor.clone())
1299                        .child(
1300                            h_flex()
1301                                .w_full()
1302                                .absolute()
1303                                .bottom_0()
1304                                .p_4()
1305                                .justify_end()
1306                                .children(self.render_send_button(cx)),
1307                        )
1308                        .into_any_element()
1309                } else {
1310                    div().into_any_element()
1311                },
1312            ))
1313    }
1314
1315    fn render_model(
1316        &self,
1317        conversation: &Model<Conversation>,
1318        cx: &mut ViewContext<Self>,
1319    ) -> impl IntoElement {
1320        Button::new("current_model", conversation.read(cx).model.display_name())
1321            .style(ButtonStyle::Filled)
1322            .tooltip(move |cx| Tooltip::text("Change Model", cx))
1323            .on_click(cx.listener(|this, _, cx| this.cycle_model(cx)))
1324    }
1325
1326    fn render_remaining_tokens(
1327        &self,
1328        conversation: &Model<Conversation>,
1329        cx: &mut ViewContext<Self>,
1330    ) -> Option<impl IntoElement> {
1331        let remaining_tokens = conversation.read(cx).remaining_tokens()?;
1332        let remaining_tokens_color = if remaining_tokens <= 0 {
1333            Color::Error
1334        } else if remaining_tokens <= 500 {
1335            Color::Warning
1336        } else {
1337            Color::Muted
1338        };
1339        Some(
1340            Label::new(remaining_tokens.to_string())
1341                .size(LabelSize::Small)
1342                .color(remaining_tokens_color),
1343        )
1344    }
1345}
1346
1347impl Render for AssistantPanel {
1348    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
1349        if let Some(authentication_prompt) = self.authentication_prompt.as_ref() {
1350            authentication_prompt.clone().into_any()
1351        } else {
1352            self.render_signed_in(cx).into_any_element()
1353        }
1354    }
1355}
1356
1357impl Panel for AssistantPanel {
1358    fn persistent_name() -> &'static str {
1359        "AssistantPanel"
1360    }
1361
1362    fn position(&self, cx: &WindowContext) -> DockPosition {
1363        match AssistantSettings::get_global(cx).dock {
1364            AssistantDockPosition::Left => DockPosition::Left,
1365            AssistantDockPosition::Bottom => DockPosition::Bottom,
1366            AssistantDockPosition::Right => DockPosition::Right,
1367        }
1368    }
1369
1370    fn position_is_valid(&self, _: DockPosition) -> bool {
1371        true
1372    }
1373
1374    fn set_position(&mut self, position: DockPosition, cx: &mut ViewContext<Self>) {
1375        settings::update_settings_file::<AssistantSettings>(self.fs.clone(), cx, move |settings| {
1376            let dock = match position {
1377                DockPosition::Left => AssistantDockPosition::Left,
1378                DockPosition::Bottom => AssistantDockPosition::Bottom,
1379                DockPosition::Right => AssistantDockPosition::Right,
1380            };
1381            settings.set_dock(dock);
1382        });
1383    }
1384
1385    fn size(&self, cx: &WindowContext) -> Pixels {
1386        let settings = AssistantSettings::get_global(cx);
1387        match self.position(cx) {
1388            DockPosition::Left | DockPosition::Right => {
1389                self.width.unwrap_or(settings.default_width)
1390            }
1391            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1392        }
1393    }
1394
1395    fn set_size(&mut self, size: Option<Pixels>, cx: &mut ViewContext<Self>) {
1396        match self.position(cx) {
1397            DockPosition::Left | DockPosition::Right => self.width = size,
1398            DockPosition::Bottom => self.height = size,
1399        }
1400        cx.notify();
1401    }
1402
1403    fn is_zoomed(&self, _: &WindowContext) -> bool {
1404        self.zoomed
1405    }
1406
1407    fn set_zoomed(&mut self, zoomed: bool, cx: &mut ViewContext<Self>) {
1408        self.zoomed = zoomed;
1409        cx.notify();
1410    }
1411
1412    fn set_active(&mut self, active: bool, cx: &mut ViewContext<Self>) {
1413        if active {
1414            let load_credentials = self.authenticate(cx);
1415            cx.spawn(|this, mut cx| async move {
1416                load_credentials.await?;
1417                this.update(&mut cx, |this, cx| {
1418                    if this.is_authenticated(cx) && this.active_conversation_editor().is_none() {
1419                        this.new_conversation(cx);
1420                    }
1421                })
1422            })
1423            .detach_and_log_err(cx);
1424        }
1425    }
1426
1427    fn icon(&self, cx: &WindowContext) -> Option<IconName> {
1428        let settings = AssistantSettings::get_global(cx);
1429        if !settings.enabled || !settings.button {
1430            return None;
1431        }
1432
1433        Some(IconName::Ai)
1434    }
1435
1436    fn icon_tooltip(&self, _cx: &WindowContext) -> Option<&'static str> {
1437        Some("Assistant Panel")
1438    }
1439
1440    fn toggle_action(&self) -> Box<dyn Action> {
1441        Box::new(ToggleFocus)
1442    }
1443}
1444
1445impl EventEmitter<PanelEvent> for AssistantPanel {}
1446
1447impl FocusableView for AssistantPanel {
1448    fn focus_handle(&self, _cx: &AppContext) -> FocusHandle {
1449        self.focus_handle.clone()
1450    }
1451}
1452
1453#[derive(Clone)]
1454enum ConversationEvent {
1455    MessagesEdited,
1456    SummaryChanged,
1457    EditSuggestionsChanged,
1458    StreamedCompletion,
1459    SlashCommandsChanged,
1460    SlashCommandOutputAdded(Range<language::Anchor>),
1461    SlashCommandOutputRemoved(Range<language::Anchor>),
1462}
1463
1464#[derive(Default)]
1465struct Summary {
1466    text: String,
1467    done: bool,
1468}
1469
1470pub struct Conversation {
1471    id: Option<String>,
1472    buffer: Model<Buffer>,
1473    pub(crate) ambient_context: AmbientContext,
1474    edit_suggestions: Vec<EditSuggestion>,
1475    slash_command_calls: Vec<SlashCommandCall>,
1476    message_anchors: Vec<MessageAnchor>,
1477    messages_metadata: HashMap<MessageId, MessageMetadata>,
1478    next_message_id: MessageId,
1479    summary: Option<Summary>,
1480    pending_summary: Task<Option<()>>,
1481    completion_count: usize,
1482    pending_completions: Vec<PendingCompletion>,
1483    model: LanguageModel,
1484    token_count: Option<usize>,
1485    pending_token_count: Task<Option<()>>,
1486    pending_edit_suggestion_parse: Option<Task<()>>,
1487    pending_command_invocation_parse: Option<Task<()>>,
1488    pending_save: Task<Result<()>>,
1489    path: Option<PathBuf>,
1490    _subscriptions: Vec<Subscription>,
1491    telemetry: Option<Arc<Telemetry>>,
1492    slash_command_registry: Arc<SlashCommandRegistry>,
1493    language_registry: Arc<LanguageRegistry>,
1494    lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1495}
1496
1497impl EventEmitter<ConversationEvent> for Conversation {}
1498
1499impl Conversation {
1500    fn new(
1501        model: LanguageModel,
1502        language_registry: Arc<LanguageRegistry>,
1503        slash_command_registry: Arc<SlashCommandRegistry>,
1504        telemetry: Option<Arc<Telemetry>>,
1505        lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1506        cx: &mut ModelContext<Self>,
1507    ) -> Self {
1508        let buffer = cx.new_model(|cx| {
1509            let mut buffer = Buffer::local("", cx);
1510            buffer.set_language_registry(language_registry.clone());
1511            buffer
1512        });
1513
1514        let mut this = Self {
1515            id: Some(Uuid::new_v4().to_string()),
1516            message_anchors: Default::default(),
1517            messages_metadata: Default::default(),
1518            next_message_id: Default::default(),
1519            ambient_context: AmbientContext::default(),
1520            edit_suggestions: Vec::new(),
1521            slash_command_calls: Vec::new(),
1522            summary: None,
1523            pending_summary: Task::ready(None),
1524            completion_count: Default::default(),
1525            pending_completions: Default::default(),
1526            token_count: None,
1527            pending_token_count: Task::ready(None),
1528            pending_edit_suggestion_parse: None,
1529            pending_command_invocation_parse: None,
1530            model,
1531            _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
1532            pending_save: Task::ready(Ok(())),
1533            path: None,
1534            buffer,
1535            telemetry,
1536            slash_command_registry,
1537            language_registry,
1538            lsp_adapter_delegate,
1539        };
1540
1541        let message = MessageAnchor {
1542            id: MessageId(post_inc(&mut this.next_message_id.0)),
1543            start: language::Anchor::MIN,
1544        };
1545        this.message_anchors.push(message.clone());
1546        this.messages_metadata.insert(
1547            message.id,
1548            MessageMetadata {
1549                role: Role::User,
1550                status: MessageStatus::Done,
1551                ambient_context: AmbientContextSnapshot::default(),
1552            },
1553        );
1554
1555        this.set_language(cx);
1556        this.count_remaining_tokens(cx);
1557        this
1558    }
1559
1560    fn serialize(&self, cx: &AppContext) -> SavedConversation {
1561        SavedConversation {
1562            id: self.id.clone(),
1563            zed: "conversation".into(),
1564            version: SavedConversation::VERSION.into(),
1565            text: self.buffer.read(cx).text(),
1566            message_metadata: self.messages_metadata.clone(),
1567            messages: self
1568                .messages(cx)
1569                .map(|message| SavedMessage {
1570                    id: message.id,
1571                    start: message.offset_range.start,
1572                })
1573                .collect(),
1574            summary: self
1575                .summary
1576                .as_ref()
1577                .map(|summary| summary.text.clone())
1578                .unwrap_or_default(),
1579        }
1580    }
1581
1582    #[allow(clippy::too_many_arguments)]
1583    async fn deserialize(
1584        saved_conversation: SavedConversation,
1585        model: LanguageModel,
1586        path: PathBuf,
1587        language_registry: Arc<LanguageRegistry>,
1588        slash_command_registry: Arc<SlashCommandRegistry>,
1589        telemetry: Option<Arc<Telemetry>>,
1590        lsp_adapter_delegate: Option<Arc<dyn LspAdapterDelegate>>,
1591        cx: &mut AsyncAppContext,
1592    ) -> Result<Model<Self>> {
1593        let id = match saved_conversation.id {
1594            Some(id) => Some(id),
1595            None => Some(Uuid::new_v4().to_string()),
1596        };
1597
1598        let markdown = language_registry.language_for_name("Markdown");
1599        let mut message_anchors = Vec::new();
1600        let mut next_message_id = MessageId(0);
1601        let buffer = cx.new_model(|cx| {
1602            let mut buffer = Buffer::local(saved_conversation.text, cx);
1603            for message in saved_conversation.messages {
1604                message_anchors.push(MessageAnchor {
1605                    id: message.id,
1606                    start: buffer.anchor_before(message.start),
1607                });
1608                next_message_id = cmp::max(next_message_id, MessageId(message.id.0 + 1));
1609            }
1610            buffer.set_language_registry(language_registry.clone());
1611            cx.spawn(|buffer, mut cx| async move {
1612                let markdown = markdown.await?;
1613                buffer.update(&mut cx, |buffer: &mut Buffer, cx| {
1614                    buffer.set_language(Some(markdown), cx)
1615                })?;
1616                anyhow::Ok(())
1617            })
1618            .detach_and_log_err(cx);
1619            buffer
1620        })?;
1621
1622        cx.new_model(move |cx| {
1623            let mut this = Self {
1624                id,
1625                message_anchors,
1626                messages_metadata: saved_conversation.message_metadata,
1627                next_message_id,
1628                ambient_context: AmbientContext::default(),
1629                edit_suggestions: Vec::new(),
1630                slash_command_calls: Vec::new(),
1631                summary: Some(Summary {
1632                    text: saved_conversation.summary,
1633                    done: true,
1634                }),
1635                pending_summary: Task::ready(None),
1636                completion_count: Default::default(),
1637                pending_completions: Default::default(),
1638                token_count: None,
1639                pending_edit_suggestion_parse: None,
1640                pending_command_invocation_parse: None,
1641                pending_token_count: Task::ready(None),
1642                model,
1643                _subscriptions: vec![cx.subscribe(&buffer, Self::handle_buffer_event)],
1644                pending_save: Task::ready(Ok(())),
1645                path: Some(path),
1646                buffer,
1647                telemetry,
1648                language_registry,
1649                slash_command_registry,
1650                lsp_adapter_delegate,
1651            };
1652            this.set_language(cx);
1653            this.reparse_edit_suggestions(cx);
1654            this.count_remaining_tokens(cx);
1655            this
1656        })
1657    }
1658
1659    fn set_language(&mut self, cx: &mut ModelContext<Self>) {
1660        let markdown = self.language_registry.language_for_name("Markdown");
1661        cx.spawn(|this, mut cx| async move {
1662            let markdown = markdown.await?;
1663            this.update(&mut cx, |this, cx| {
1664                this.buffer
1665                    .update(cx, |buffer, cx| buffer.set_language(Some(markdown), cx));
1666            })
1667        })
1668        .detach_and_log_err(cx);
1669    }
1670
1671    fn toggle_recent_buffers(&mut self, cx: &mut ModelContext<Self>) {
1672        self.ambient_context.recent_buffers.enabled = !self.ambient_context.recent_buffers.enabled;
1673        match self.ambient_context.recent_buffers.update(cx) {
1674            ContextUpdated::Updating => {}
1675            ContextUpdated::Disabled => {
1676                self.count_remaining_tokens(cx);
1677            }
1678        }
1679    }
1680
1681    fn toggle_current_project_context(
1682        &mut self,
1683        fs: Arc<dyn Fs>,
1684        project: WeakModel<Project>,
1685        cx: &mut ModelContext<Self>,
1686    ) {
1687        self.ambient_context.current_project.enabled =
1688            !self.ambient_context.current_project.enabled;
1689        match self.ambient_context.current_project.update(fs, project, cx) {
1690            ContextUpdated::Updating => {}
1691            ContextUpdated::Disabled => {
1692                self.count_remaining_tokens(cx);
1693            }
1694        }
1695    }
1696
1697    fn set_recent_buffers(
1698        &mut self,
1699        buffers: impl IntoIterator<Item = Model<Buffer>>,
1700        cx: &mut ModelContext<Self>,
1701    ) {
1702        self.ambient_context.recent_buffers.buffers.clear();
1703        self.ambient_context
1704            .recent_buffers
1705            .buffers
1706            .extend(buffers.into_iter().map(|buffer| RecentBuffer {
1707                buffer: buffer.downgrade(),
1708                _subscription: cx.observe(&buffer, |this, _, cx| {
1709                    match this.ambient_context.recent_buffers.update(cx) {
1710                        ContextUpdated::Updating => {}
1711                        ContextUpdated::Disabled => {
1712                            this.count_remaining_tokens(cx);
1713                        }
1714                    }
1715                }),
1716            }));
1717        match self.ambient_context.recent_buffers.update(cx) {
1718            ContextUpdated::Updating => {}
1719            ContextUpdated::Disabled => {
1720                self.count_remaining_tokens(cx);
1721            }
1722        }
1723    }
1724
1725    fn handle_buffer_event(
1726        &mut self,
1727        _: Model<Buffer>,
1728        event: &language::Event,
1729        cx: &mut ModelContext<Self>,
1730    ) {
1731        if *event == language::Event::Edited {
1732            self.count_remaining_tokens(cx);
1733            self.reparse_edit_suggestions(cx);
1734            self.reparse_slash_command_calls(cx);
1735            cx.emit(ConversationEvent::MessagesEdited);
1736        }
1737    }
1738
1739    pub(crate) fn count_remaining_tokens(&mut self, cx: &mut ModelContext<Self>) {
1740        let request = self.to_completion_request(cx);
1741        self.pending_token_count = cx.spawn(|this, mut cx| {
1742            async move {
1743                cx.background_executor()
1744                    .timer(Duration::from_millis(200))
1745                    .await;
1746
1747                let token_count = cx
1748                    .update(|cx| CompletionProvider::global(cx).count_tokens(request, cx))?
1749                    .await?;
1750
1751                this.update(&mut cx, |this, cx| {
1752                    this.token_count = Some(token_count);
1753                    cx.notify()
1754                })?;
1755                anyhow::Ok(())
1756            }
1757            .log_err()
1758        });
1759    }
1760
1761    fn reparse_edit_suggestions(&mut self, cx: &mut ModelContext<Self>) {
1762        self.pending_edit_suggestion_parse = Some(cx.spawn(|this, mut cx| async move {
1763            cx.background_executor()
1764                .timer(Duration::from_millis(200))
1765                .await;
1766
1767            this.update(&mut cx, |this, cx| {
1768                this.reparse_edit_suggestions_in_range(0..this.buffer.read(cx).len(), cx);
1769            })
1770            .ok();
1771        }));
1772    }
1773
1774    fn reparse_edit_suggestions_in_range(
1775        &mut self,
1776        range: Range<usize>,
1777        cx: &mut ModelContext<Self>,
1778    ) {
1779        self.buffer.update(cx, |buffer, _| {
1780            let range_start = buffer.anchor_before(range.start);
1781            let range_end = buffer.anchor_after(range.end);
1782            let start_ix = self
1783                .edit_suggestions
1784                .binary_search_by(|probe| {
1785                    probe
1786                        .source_range
1787                        .end
1788                        .cmp(&range_start, buffer)
1789                        .then(Ordering::Greater)
1790                })
1791                .unwrap_err();
1792            let end_ix = self
1793                .edit_suggestions
1794                .binary_search_by(|probe| {
1795                    probe
1796                        .source_range
1797                        .start
1798                        .cmp(&range_end, buffer)
1799                        .then(Ordering::Less)
1800                })
1801                .unwrap_err();
1802
1803            let mut new_edit_suggestions = Vec::new();
1804            let mut message_lines = buffer.as_rope().chunks_in_range(range).lines();
1805            while let Some(suggestion) = parse_next_edit_suggestion(&mut message_lines) {
1806                let start_anchor = buffer.anchor_after(suggestion.outer_range.start);
1807                let end_anchor = buffer.anchor_before(suggestion.outer_range.end);
1808                new_edit_suggestions.push(EditSuggestion {
1809                    source_range: start_anchor..end_anchor,
1810                    full_path: suggestion.path,
1811                });
1812            }
1813            self.edit_suggestions
1814                .splice(start_ix..end_ix, new_edit_suggestions);
1815        });
1816        cx.emit(ConversationEvent::EditSuggestionsChanged);
1817        cx.notify();
1818    }
1819
1820    fn reparse_slash_command_calls(&mut self, cx: &mut ModelContext<Self>) {
1821        self.pending_command_invocation_parse = Some(cx.spawn(|this, mut cx| async move {
1822            cx.background_executor().timer(SLASH_COMMAND_DEBOUNCE).await;
1823
1824            this.update(&mut cx, |this, cx| {
1825                let buffer = this.buffer.read(cx).snapshot();
1826
1827                let mut changed = false;
1828                let mut new_calls = Vec::new();
1829                let mut old_calls = mem::take(&mut this.slash_command_calls)
1830                    .into_iter()
1831                    .peekable();
1832                let mut lines = buffer.as_rope().chunks().lines();
1833                let mut offset = 0;
1834                while let Some(line) = lines.next() {
1835                    let line_end_offset = offset + line.len();
1836                    if let Some(call) = SlashCommandLine::parse(line) {
1837                        let mut unchanged_call = None;
1838                        while let Some(old_call) = old_calls.peek() {
1839                            match old_call.source_range.start.to_offset(&buffer).cmp(&offset) {
1840                                Ordering::Greater => break,
1841                                Ordering::Equal
1842                                    if this.slash_command_is_unchanged(
1843                                        old_call, &call, line, &buffer,
1844                                    ) =>
1845                                {
1846                                    unchanged_call = old_calls.next();
1847                                }
1848                                _ => {
1849                                    changed = true;
1850                                    let old_call = old_calls.next().unwrap();
1851                                    this.slash_command_call_removed(old_call, cx);
1852                                }
1853                            }
1854                        }
1855
1856                        let name = &line[call.name];
1857                        if let Some(call) = unchanged_call {
1858                            new_calls.push(call);
1859                        } else if let Some((command, lsp_adapter_delegate)) = this
1860                            .slash_command_registry
1861                            .command(name)
1862                            .zip(this.lsp_adapter_delegate.clone())
1863                        {
1864                            changed = true;
1865                            let name = name.to_string();
1866                            let source_range =
1867                                buffer.anchor_after(offset)..buffer.anchor_before(line_end_offset);
1868
1869                            let argument = call.argument.map(|range| &line[range]);
1870                            let invocation = command.run(argument, lsp_adapter_delegate, cx);
1871
1872                            new_calls.push(SlashCommandCall {
1873                                name,
1874                                argument: argument.map(|s| s.to_string()),
1875                                source_range: source_range.clone(),
1876                                output_range: None,
1877                                should_rerun: false,
1878                                _invalidate: cx.spawn(|this, mut cx| {
1879                                    let source_range = source_range.clone();
1880                                    let invalidated = invocation.invalidated;
1881                                    async move {
1882                                        if invalidated.await.is_ok() {
1883                                            _ = this.update(&mut cx, |this, cx| {
1884                                                let buffer = this.buffer.read(cx);
1885                                                let call_ix = this
1886                                                    .slash_command_calls
1887                                                    .binary_search_by(|probe| {
1888                                                        probe
1889                                                            .source_range
1890                                                            .start
1891                                                            .cmp(&source_range.start, buffer)
1892                                                    });
1893                                                if let Ok(call_ix) = call_ix {
1894                                                    this.slash_command_calls[call_ix]
1895                                                        .should_rerun = true;
1896                                                    this.reparse_slash_command_calls(cx);
1897                                                }
1898                                            });
1899                                        }
1900                                    }
1901                                }),
1902                                _command_cleanup: invocation.cleanup,
1903                            });
1904
1905                            cx.spawn(|this, mut cx| async move {
1906                                let output = invocation.output.await;
1907                                this.update(&mut cx, |this, cx| {
1908                                    let output_range = this.buffer.update(cx, |buffer, cx| {
1909                                        let call_ix = this
1910                                            .slash_command_calls
1911                                            .binary_search_by(|probe| {
1912                                                probe
1913                                                    .source_range
1914                                                    .start
1915                                                    .cmp(&source_range.start, buffer)
1916                                            })
1917                                            .ok()?;
1918
1919                                        let mut output = output.log_err()?;
1920                                        output.truncate(output.trim_end().len());
1921
1922                                        let source_end = source_range.end.to_offset(buffer);
1923                                        let output_start = source_end + '\n'.len_utf8();
1924                                        let output_end = output_start + output.len();
1925
1926                                        if buffer
1927                                            .chars_at(source_end)
1928                                            .next()
1929                                            .map_or(false, |c| c != '\n')
1930                                        {
1931                                            output.push('\n');
1932                                        }
1933
1934                                        buffer.edit(
1935                                            [
1936                                                (source_end..source_end, "\n".to_string()),
1937                                                (source_end..source_end, output),
1938                                            ],
1939                                            None,
1940                                            cx,
1941                                        );
1942
1943                                        let output_start = buffer.anchor_after(output_start);
1944                                        let output_end = buffer.anchor_before(output_end);
1945                                        this.slash_command_calls[call_ix].output_range =
1946                                            Some(output_start..output_end);
1947                                        Some(source_range.end..output_end)
1948                                    });
1949                                    if let Some(output_range) = output_range {
1950                                        cx.emit(ConversationEvent::SlashCommandOutputAdded(
1951                                            output_range,
1952                                        ));
1953                                        cx.emit(ConversationEvent::SlashCommandsChanged);
1954                                    }
1955                                })
1956                                .ok();
1957                            })
1958                            .detach();
1959                        }
1960                    }
1961                    offset = lines.offset();
1962                }
1963
1964                for old_call in old_calls {
1965                    changed = true;
1966                    this.slash_command_call_removed(old_call, cx);
1967                }
1968
1969                if changed {
1970                    cx.emit(ConversationEvent::SlashCommandsChanged);
1971                }
1972
1973                this.slash_command_calls = new_calls;
1974            })
1975            .ok();
1976        }));
1977    }
1978
1979    fn slash_command_is_unchanged(
1980        &self,
1981        old_call: &SlashCommandCall,
1982        new_call: &SlashCommandLine,
1983        new_text: &str,
1984        buffer: &BufferSnapshot,
1985    ) -> bool {
1986        if old_call.name != new_text[new_call.name.clone()] {
1987            return false;
1988        }
1989
1990        if old_call.argument.as_deref() != new_call.argument.clone().map(|range| &new_text[range]) {
1991            return false;
1992        }
1993
1994        if old_call.should_rerun {
1995            return false;
1996        }
1997
1998        if let Some(output_range) = &old_call.output_range {
1999            let source_range = old_call.source_range.to_point(buffer);
2000            let output_start = output_range.start.to_point(buffer);
2001            if source_range.start.column != 0 {
2002                return false;
2003            }
2004            if source_range.end.column != new_text.len() as u32 {
2005                return false;
2006            }
2007            if output_start != Point::new(source_range.end.row + 1, 0) {
2008                return false;
2009            }
2010            if let Some(next_char) = buffer.chars_at(output_range.end).next() {
2011                if next_char != '\n' {
2012                    return false;
2013                }
2014            }
2015        }
2016        true
2017    }
2018
2019    fn slash_command_call_removed(
2020        &self,
2021        old_call: SlashCommandCall,
2022        cx: &mut ModelContext<Conversation>,
2023    ) {
2024        if let Some(output_range) = old_call.output_range {
2025            self.buffer.update(cx, |buffer, cx| {
2026                buffer.edit(
2027                    [(old_call.source_range.end..output_range.end, "")],
2028                    None,
2029                    cx,
2030                );
2031            });
2032            cx.emit(ConversationEvent::SlashCommandOutputRemoved(
2033                old_call.source_range.end..output_range.end,
2034            ))
2035        }
2036    }
2037
2038    fn remaining_tokens(&self) -> Option<isize> {
2039        Some(self.model.max_token_count() as isize - self.token_count? as isize)
2040    }
2041
2042    fn set_model(&mut self, model: LanguageModel, cx: &mut ModelContext<Self>) {
2043        self.model = model;
2044        self.count_remaining_tokens(cx);
2045    }
2046
2047    fn assist(
2048        &mut self,
2049        selected_messages: HashSet<MessageId>,
2050        cx: &mut ModelContext<Self>,
2051    ) -> Vec<MessageAnchor> {
2052        let mut user_messages = Vec::new();
2053
2054        let last_message_id = if let Some(last_message_id) =
2055            self.message_anchors.iter().rev().find_map(|message| {
2056                message
2057                    .start
2058                    .is_valid(self.buffer.read(cx))
2059                    .then_some(message.id)
2060            }) {
2061            last_message_id
2062        } else {
2063            return Default::default();
2064        };
2065
2066        let mut should_assist = false;
2067        for selected_message_id in selected_messages {
2068            let selected_message_role =
2069                if let Some(metadata) = self.messages_metadata.get(&selected_message_id) {
2070                    metadata.role
2071                } else {
2072                    continue;
2073                };
2074
2075            if selected_message_role == Role::Assistant {
2076                if let Some(user_message) = self.insert_message_after(
2077                    selected_message_id,
2078                    Role::User,
2079                    MessageStatus::Done,
2080                    cx,
2081                ) {
2082                    user_messages.push(user_message);
2083                }
2084            } else {
2085                should_assist = true;
2086            }
2087        }
2088
2089        if should_assist {
2090            if !CompletionProvider::global(cx).is_authenticated() {
2091                log::info!("completion provider has no credentials");
2092                return Default::default();
2093            }
2094
2095            let request = self.to_completion_request(cx);
2096            let stream = CompletionProvider::global(cx).complete(request);
2097            let assistant_message = self
2098                .insert_message_after(last_message_id, Role::Assistant, MessageStatus::Pending, cx)
2099                .unwrap();
2100
2101            // Queue up the user's next reply.
2102            let user_message = self
2103                .insert_message_after(assistant_message.id, Role::User, MessageStatus::Done, cx)
2104                .unwrap();
2105            user_messages.push(user_message);
2106
2107            let task = cx.spawn({
2108                |this, mut cx| async move {
2109                    let assistant_message_id = assistant_message.id;
2110                    let mut response_latency = None;
2111                    let stream_completion = async {
2112                        let request_start = Instant::now();
2113                        let mut messages = stream.await?;
2114
2115                        while let Some(message) = messages.next().await {
2116                            if response_latency.is_none() {
2117                                response_latency = Some(request_start.elapsed());
2118                            }
2119                            let text = message?;
2120
2121                            this.update(&mut cx, |this, cx| {
2122                                let message_ix = this
2123                                    .message_anchors
2124                                    .iter()
2125                                    .position(|message| message.id == assistant_message_id)?;
2126                                let message_range = this.buffer.update(cx, |buffer, cx| {
2127                                    let message_start_offset =
2128                                        this.message_anchors[message_ix].start.to_offset(buffer);
2129                                    let message_old_end_offset = this.message_anchors
2130                                        [message_ix + 1..]
2131                                        .iter()
2132                                        .find(|message| message.start.is_valid(buffer))
2133                                        .map_or(buffer.len(), |message| {
2134                                            message.start.to_offset(buffer).saturating_sub(1)
2135                                        });
2136                                    let message_new_end_offset =
2137                                        message_old_end_offset + text.len();
2138                                    buffer.edit(
2139                                        [(message_old_end_offset..message_old_end_offset, text)],
2140                                        None,
2141                                        cx,
2142                                    );
2143                                    message_start_offset..message_new_end_offset
2144                                });
2145                                this.reparse_edit_suggestions_in_range(message_range, cx);
2146                                cx.emit(ConversationEvent::StreamedCompletion);
2147
2148                                Some(())
2149                            })?;
2150                            smol::future::yield_now().await;
2151                        }
2152
2153                        this.update(&mut cx, |this, cx| {
2154                            this.pending_completions
2155                                .retain(|completion| completion.id != this.completion_count);
2156                            this.summarize(cx);
2157                        })?;
2158
2159                        anyhow::Ok(())
2160                    };
2161
2162                    let result = stream_completion.await;
2163
2164                    this.update(&mut cx, |this, cx| {
2165                        if let Some(metadata) =
2166                            this.messages_metadata.get_mut(&assistant_message.id)
2167                        {
2168                            let error_message = result
2169                                .err()
2170                                .map(|error| error.to_string().trim().to_string());
2171                            if let Some(error_message) = error_message.as_ref() {
2172                                metadata.status =
2173                                    MessageStatus::Error(SharedString::from(error_message.clone()));
2174                            } else {
2175                                metadata.status = MessageStatus::Done;
2176                            }
2177
2178                            if let Some(telemetry) = this.telemetry.as_ref() {
2179                                telemetry.report_assistant_event(
2180                                    this.id.clone(),
2181                                    AssistantKind::Panel,
2182                                    this.model.telemetry_id(),
2183                                    response_latency,
2184                                    error_message,
2185                                );
2186                            }
2187
2188                            cx.emit(ConversationEvent::MessagesEdited);
2189                        }
2190                    })
2191                    .ok();
2192                }
2193            });
2194
2195            self.pending_completions.push(PendingCompletion {
2196                id: post_inc(&mut self.completion_count),
2197                _task: task,
2198            });
2199        }
2200
2201        user_messages
2202    }
2203
2204    fn to_completion_request(&self, cx: &mut ModelContext<Conversation>) -> LanguageModelRequest {
2205        let edits_system_prompt = LanguageModelRequestMessage {
2206            role: Role::System,
2207            content: include_str!("./system_prompts/edits.md").to_string(),
2208        };
2209
2210        let recent_buffers_context = self.ambient_context.recent_buffers.to_message();
2211        let current_project_context = self.ambient_context.current_project.to_message();
2212
2213        let messages = Some(edits_system_prompt)
2214            .into_iter()
2215            .chain(recent_buffers_context)
2216            .chain(current_project_context)
2217            .chain(
2218                self.messages(cx)
2219                    .filter(|message| matches!(message.status, MessageStatus::Done))
2220                    .map(|message| message.to_request_message(self.buffer.read(cx))),
2221            );
2222
2223        LanguageModelRequest {
2224            model: self.model.clone(),
2225            messages: messages.collect(),
2226            stop: vec![],
2227            temperature: 1.0,
2228        }
2229    }
2230
2231    fn cancel_last_assist(&mut self) -> bool {
2232        self.pending_completions.pop().is_some()
2233    }
2234
2235    fn cycle_message_roles(&mut self, ids: HashSet<MessageId>, cx: &mut ModelContext<Self>) {
2236        for id in ids {
2237            if let Some(metadata) = self.messages_metadata.get_mut(&id) {
2238                metadata.role.cycle();
2239                cx.emit(ConversationEvent::MessagesEdited);
2240                cx.notify();
2241            }
2242        }
2243    }
2244
2245    fn insert_message_after(
2246        &mut self,
2247        message_id: MessageId,
2248        role: Role,
2249        status: MessageStatus,
2250        cx: &mut ModelContext<Self>,
2251    ) -> Option<MessageAnchor> {
2252        if let Some(prev_message_ix) = self
2253            .message_anchors
2254            .iter()
2255            .position(|message| message.id == message_id)
2256        {
2257            // Find the next valid message after the one we were given.
2258            let mut next_message_ix = prev_message_ix + 1;
2259            while let Some(next_message) = self.message_anchors.get(next_message_ix) {
2260                if next_message.start.is_valid(self.buffer.read(cx)) {
2261                    break;
2262                }
2263                next_message_ix += 1;
2264            }
2265
2266            let start = self.buffer.update(cx, |buffer, cx| {
2267                let offset = self
2268                    .message_anchors
2269                    .get(next_message_ix)
2270                    .map_or(buffer.len(), |message| message.start.to_offset(buffer) - 1);
2271                buffer.edit([(offset..offset, "\n")], None, cx);
2272                buffer.anchor_before(offset + 1)
2273            });
2274            let message = MessageAnchor {
2275                id: MessageId(post_inc(&mut self.next_message_id.0)),
2276                start,
2277            };
2278            self.message_anchors
2279                .insert(next_message_ix, message.clone());
2280            self.messages_metadata.insert(
2281                message.id,
2282                MessageMetadata {
2283                    role,
2284                    status,
2285                    ambient_context: self.ambient_context.snapshot(),
2286                },
2287            );
2288            cx.emit(ConversationEvent::MessagesEdited);
2289            Some(message)
2290        } else {
2291            None
2292        }
2293    }
2294
2295    fn split_message(
2296        &mut self,
2297        range: Range<usize>,
2298        cx: &mut ModelContext<Self>,
2299    ) -> (Option<MessageAnchor>, Option<MessageAnchor>) {
2300        let start_message = self.message_for_offset(range.start, cx);
2301        let end_message = self.message_for_offset(range.end, cx);
2302        if let Some((start_message, end_message)) = start_message.zip(end_message) {
2303            // Prevent splitting when range spans multiple messages.
2304            if start_message.id != end_message.id {
2305                return (None, None);
2306            }
2307
2308            let message = start_message;
2309            let role = message.role;
2310            let mut edited_buffer = false;
2311
2312            let mut suffix_start = None;
2313            if range.start > message.offset_range.start && range.end < message.offset_range.end - 1
2314            {
2315                if self.buffer.read(cx).chars_at(range.end).next() == Some('\n') {
2316                    suffix_start = Some(range.end + 1);
2317                } else if self.buffer.read(cx).reversed_chars_at(range.end).next() == Some('\n') {
2318                    suffix_start = Some(range.end);
2319                }
2320            }
2321
2322            let suffix = if let Some(suffix_start) = suffix_start {
2323                MessageAnchor {
2324                    id: MessageId(post_inc(&mut self.next_message_id.0)),
2325                    start: self.buffer.read(cx).anchor_before(suffix_start),
2326                }
2327            } else {
2328                self.buffer.update(cx, |buffer, cx| {
2329                    buffer.edit([(range.end..range.end, "\n")], None, cx);
2330                });
2331                edited_buffer = true;
2332                MessageAnchor {
2333                    id: MessageId(post_inc(&mut self.next_message_id.0)),
2334                    start: self.buffer.read(cx).anchor_before(range.end + 1),
2335                }
2336            };
2337
2338            self.message_anchors
2339                .insert(message.index_range.end + 1, suffix.clone());
2340            self.messages_metadata.insert(
2341                suffix.id,
2342                MessageMetadata {
2343                    role,
2344                    status: MessageStatus::Done,
2345                    ambient_context: message.ambient_context.clone(),
2346                },
2347            );
2348
2349            let new_messages =
2350                if range.start == range.end || range.start == message.offset_range.start {
2351                    (None, Some(suffix))
2352                } else {
2353                    let mut prefix_end = None;
2354                    if range.start > message.offset_range.start
2355                        && range.end < message.offset_range.end - 1
2356                    {
2357                        if self.buffer.read(cx).chars_at(range.start).next() == Some('\n') {
2358                            prefix_end = Some(range.start + 1);
2359                        } else if self.buffer.read(cx).reversed_chars_at(range.start).next()
2360                            == Some('\n')
2361                        {
2362                            prefix_end = Some(range.start);
2363                        }
2364                    }
2365
2366                    let selection = if let Some(prefix_end) = prefix_end {
2367                        cx.emit(ConversationEvent::MessagesEdited);
2368                        MessageAnchor {
2369                            id: MessageId(post_inc(&mut self.next_message_id.0)),
2370                            start: self.buffer.read(cx).anchor_before(prefix_end),
2371                        }
2372                    } else {
2373                        self.buffer.update(cx, |buffer, cx| {
2374                            buffer.edit([(range.start..range.start, "\n")], None, cx)
2375                        });
2376                        edited_buffer = true;
2377                        MessageAnchor {
2378                            id: MessageId(post_inc(&mut self.next_message_id.0)),
2379                            start: self.buffer.read(cx).anchor_before(range.end + 1),
2380                        }
2381                    };
2382
2383                    self.message_anchors
2384                        .insert(message.index_range.end + 1, selection.clone());
2385                    self.messages_metadata.insert(
2386                        selection.id,
2387                        MessageMetadata {
2388                            role,
2389                            status: MessageStatus::Done,
2390                            ambient_context: message.ambient_context,
2391                        },
2392                    );
2393                    (Some(selection), Some(suffix))
2394                };
2395
2396            if !edited_buffer {
2397                cx.emit(ConversationEvent::MessagesEdited);
2398            }
2399            new_messages
2400        } else {
2401            (None, None)
2402        }
2403    }
2404
2405    fn summarize(&mut self, cx: &mut ModelContext<Self>) {
2406        if self.message_anchors.len() >= 2 && self.summary.is_none() {
2407            if !CompletionProvider::global(cx).is_authenticated() {
2408                return;
2409            }
2410
2411            let messages = self
2412                .messages(cx)
2413                .take(2)
2414                .map(|message| message.to_request_message(self.buffer.read(cx)))
2415                .chain(Some(LanguageModelRequestMessage {
2416                    role: Role::User,
2417                    content: "Summarize the conversation into a short title without punctuation"
2418                        .into(),
2419                }));
2420            let request = LanguageModelRequest {
2421                model: self.model.clone(),
2422                messages: messages.collect(),
2423                stop: vec![],
2424                temperature: 1.0,
2425            };
2426
2427            let stream = CompletionProvider::global(cx).complete(request);
2428            self.pending_summary = cx.spawn(|this, mut cx| {
2429                async move {
2430                    let mut messages = stream.await?;
2431
2432                    while let Some(message) = messages.next().await {
2433                        let text = message?;
2434                        this.update(&mut cx, |this, cx| {
2435                            this.summary
2436                                .get_or_insert(Default::default())
2437                                .text
2438                                .push_str(&text);
2439                            cx.emit(ConversationEvent::SummaryChanged);
2440                        })?;
2441                    }
2442
2443                    this.update(&mut cx, |this, cx| {
2444                        if let Some(summary) = this.summary.as_mut() {
2445                            summary.done = true;
2446                            cx.emit(ConversationEvent::SummaryChanged);
2447                        }
2448                    })?;
2449
2450                    anyhow::Ok(())
2451                }
2452                .log_err()
2453            });
2454        }
2455    }
2456
2457    fn message_for_offset(&self, offset: usize, cx: &AppContext) -> Option<Message> {
2458        self.messages_for_offsets([offset], cx).pop()
2459    }
2460
2461    fn messages_for_offsets(
2462        &self,
2463        offsets: impl IntoIterator<Item = usize>,
2464        cx: &AppContext,
2465    ) -> Vec<Message> {
2466        let mut result = Vec::new();
2467
2468        let mut messages = self.messages(cx).peekable();
2469        let mut offsets = offsets.into_iter().peekable();
2470        let mut current_message = messages.next();
2471        while let Some(offset) = offsets.next() {
2472            // Locate the message that contains the offset.
2473            while current_message.as_ref().map_or(false, |message| {
2474                !message.offset_range.contains(&offset) && messages.peek().is_some()
2475            }) {
2476                current_message = messages.next();
2477            }
2478            let Some(message) = current_message.as_ref() else {
2479                break;
2480            };
2481
2482            // Skip offsets that are in the same message.
2483            while offsets.peek().map_or(false, |offset| {
2484                message.offset_range.contains(offset) || messages.peek().is_none()
2485            }) {
2486                offsets.next();
2487            }
2488
2489            result.push(message.clone());
2490        }
2491        result
2492    }
2493
2494    fn messages<'a>(&'a self, cx: &'a AppContext) -> impl 'a + Iterator<Item = Message> {
2495        let buffer = self.buffer.read(cx);
2496        let mut slash_command_calls = self
2497            .slash_command_calls
2498            .iter()
2499            .map(|call| {
2500                if let Some(output) = &call.output_range {
2501                    call.source_range.start.to_offset(buffer)..output.start.to_offset(buffer)
2502                } else {
2503                    call.source_range.to_offset(buffer)
2504                }
2505            })
2506            .peekable();
2507        let mut message_anchors = self.message_anchors.iter().enumerate().peekable();
2508        iter::from_fn(move || {
2509            if let Some((start_ix, message_anchor)) = message_anchors.next() {
2510                let metadata = self.messages_metadata.get(&message_anchor.id)?;
2511                let message_start = message_anchor.start.to_offset(buffer);
2512                let mut message_end = None;
2513                let mut end_ix = start_ix;
2514                while let Some((_, next_message)) = message_anchors.peek() {
2515                    if next_message.start.is_valid(buffer) {
2516                        message_end = Some(next_message.start);
2517                        break;
2518                    } else {
2519                        end_ix += 1;
2520                        message_anchors.next();
2521                    }
2522                }
2523                let message_end = message_end
2524                    .unwrap_or(language::Anchor::MAX)
2525                    .to_offset(buffer);
2526
2527                let mut slash_command_ranges = Vec::new();
2528                while let Some(call_range) = slash_command_calls.peek() {
2529                    if call_range.end <= message_end {
2530                        slash_command_ranges.push(slash_command_calls.next().unwrap());
2531                    } else {
2532                        break;
2533                    }
2534                }
2535
2536                return Some(Message {
2537                    index_range: start_ix..end_ix,
2538                    offset_range: message_start..message_end,
2539                    id: message_anchor.id,
2540                    anchor: message_anchor.start,
2541                    role: metadata.role,
2542                    status: metadata.status.clone(),
2543                    slash_command_ranges,
2544                    ambient_context: metadata.ambient_context.clone(),
2545                });
2546            }
2547            None
2548        })
2549    }
2550
2551    fn save(
2552        &mut self,
2553        debounce: Option<Duration>,
2554        fs: Arc<dyn Fs>,
2555        cx: &mut ModelContext<Conversation>,
2556    ) {
2557        self.pending_save = cx.spawn(|this, mut cx| async move {
2558            if let Some(debounce) = debounce {
2559                cx.background_executor().timer(debounce).await;
2560            }
2561
2562            let (old_path, summary) = this.read_with(&cx, |this, _| {
2563                let path = this.path.clone();
2564                let summary = if let Some(summary) = this.summary.as_ref() {
2565                    if summary.done {
2566                        Some(summary.text.clone())
2567                    } else {
2568                        None
2569                    }
2570                } else {
2571                    None
2572                };
2573                (path, summary)
2574            })?;
2575
2576            if let Some(summary) = summary {
2577                let conversation = this.read_with(&cx, |this, cx| this.serialize(cx))?;
2578                let path = if let Some(old_path) = old_path {
2579                    old_path
2580                } else {
2581                    let mut discriminant = 1;
2582                    let mut new_path;
2583                    loop {
2584                        new_path = CONVERSATIONS_DIR.join(&format!(
2585                            "{} - {}.zed.json",
2586                            summary.trim(),
2587                            discriminant
2588                        ));
2589                        if fs.is_file(&new_path).await {
2590                            discriminant += 1;
2591                        } else {
2592                            break;
2593                        }
2594                    }
2595                    new_path
2596                };
2597
2598                fs.create_dir(CONVERSATIONS_DIR.as_ref()).await?;
2599                fs.atomic_write(path.clone(), serde_json::to_string(&conversation).unwrap())
2600                    .await?;
2601                this.update(&mut cx, |this, _| this.path = Some(path))?;
2602            }
2603
2604            Ok(())
2605        });
2606    }
2607}
2608
2609#[derive(Debug)]
2610enum EditParsingState {
2611    None,
2612    InOldText {
2613        path: PathBuf,
2614        start_offset: usize,
2615        old_text_start_offset: usize,
2616    },
2617    InNewText {
2618        path: PathBuf,
2619        start_offset: usize,
2620        old_text_range: Range<usize>,
2621        new_text_start_offset: usize,
2622    },
2623}
2624
2625#[derive(Clone, Debug, PartialEq)]
2626struct EditSuggestion {
2627    source_range: Range<language::Anchor>,
2628    full_path: PathBuf,
2629}
2630
2631struct ParsedEditSuggestion {
2632    path: PathBuf,
2633    outer_range: Range<usize>,
2634    old_text_range: Range<usize>,
2635    new_text_range: Range<usize>,
2636}
2637
2638fn parse_next_edit_suggestion(lines: &mut rope::Lines) -> Option<ParsedEditSuggestion> {
2639    let mut state = EditParsingState::None;
2640    loop {
2641        let offset = lines.offset();
2642        let message_line = lines.next()?;
2643        match state {
2644            EditParsingState::None => {
2645                if let Some(rest) = message_line.strip_prefix("```edit ") {
2646                    let path = rest.trim();
2647                    if !path.is_empty() {
2648                        state = EditParsingState::InOldText {
2649                            path: PathBuf::from(path),
2650                            start_offset: offset,
2651                            old_text_start_offset: lines.offset(),
2652                        };
2653                    }
2654                }
2655            }
2656            EditParsingState::InOldText {
2657                path,
2658                start_offset,
2659                old_text_start_offset,
2660            } => {
2661                if message_line == "---" {
2662                    state = EditParsingState::InNewText {
2663                        path,
2664                        start_offset,
2665                        old_text_range: old_text_start_offset..offset,
2666                        new_text_start_offset: lines.offset(),
2667                    };
2668                } else {
2669                    state = EditParsingState::InOldText {
2670                        path,
2671                        start_offset,
2672                        old_text_start_offset,
2673                    };
2674                }
2675            }
2676            EditParsingState::InNewText {
2677                path,
2678                start_offset,
2679                old_text_range,
2680                new_text_start_offset,
2681            } => {
2682                if message_line == "```" {
2683                    return Some(ParsedEditSuggestion {
2684                        path,
2685                        outer_range: start_offset..offset + "```".len(),
2686                        old_text_range,
2687                        new_text_range: new_text_start_offset..offset,
2688                    });
2689                } else {
2690                    state = EditParsingState::InNewText {
2691                        path,
2692                        start_offset,
2693                        old_text_range,
2694                        new_text_start_offset,
2695                    };
2696                }
2697            }
2698        }
2699    }
2700}
2701
2702struct SlashCommandCall {
2703    source_range: Range<language::Anchor>,
2704    output_range: Option<Range<language::Anchor>>,
2705    name: String,
2706    argument: Option<String>,
2707    should_rerun: bool,
2708    _invalidate: Task<()>,
2709    _command_cleanup: SlashCommandCleanup,
2710}
2711
2712struct PendingCompletion {
2713    id: usize,
2714    _task: Task<()>,
2715}
2716
2717enum ConversationEditorEvent {
2718    TabContentChanged,
2719}
2720
2721#[derive(Copy, Clone, Debug, PartialEq)]
2722struct ScrollPosition {
2723    offset_before_cursor: gpui::Point<f32>,
2724    cursor: Anchor,
2725}
2726
2727struct ConversationEditor {
2728    conversation: Model<Conversation>,
2729    fs: Arc<dyn Fs>,
2730    workspace: WeakView<Workspace>,
2731    editor: View<Editor>,
2732    flap_ids: HashMap<Range<language::Anchor>, FlapId>,
2733    blocks: HashSet<BlockId>,
2734    scroll_position: Option<ScrollPosition>,
2735    _subscriptions: Vec<Subscription>,
2736}
2737
2738impl ConversationEditor {
2739    fn new(
2740        model: LanguageModel,
2741        language_registry: Arc<LanguageRegistry>,
2742        slash_command_registry: Arc<SlashCommandRegistry>,
2743        fs: Arc<dyn Fs>,
2744        workspace: View<Workspace>,
2745        cx: &mut ViewContext<Self>,
2746    ) -> Self {
2747        let telemetry = workspace.read(cx).client().telemetry().clone();
2748        let project = workspace.read(cx).project().clone();
2749        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err();
2750
2751        let conversation = cx.new_model(|cx| {
2752            Conversation::new(
2753                model,
2754                language_registry,
2755                slash_command_registry,
2756                Some(telemetry),
2757                lsp_adapter_delegate,
2758                cx,
2759            )
2760        });
2761        Self::for_conversation(conversation, fs, workspace, cx)
2762    }
2763
2764    fn for_conversation(
2765        conversation: Model<Conversation>,
2766        fs: Arc<dyn Fs>,
2767        workspace: View<Workspace>,
2768        cx: &mut ViewContext<Self>,
2769    ) -> Self {
2770        let command_registry = conversation.read(cx).slash_command_registry.clone();
2771        let completion_provider = SlashCommandCompletionProvider::new(command_registry);
2772
2773        let editor = cx.new_view(|cx| {
2774            let mut editor = Editor::for_buffer(conversation.read(cx).buffer.clone(), None, cx);
2775            editor.set_soft_wrap_mode(SoftWrap::EditorWidth, cx);
2776            editor.set_show_line_numbers(false, cx);
2777            editor.set_show_git_diff_gutter(false, cx);
2778            editor.set_show_code_actions(false, cx);
2779            editor.set_show_wrap_guides(false, cx);
2780            editor.set_show_indent_guides(false, cx);
2781            editor.set_completion_provider(Box::new(completion_provider));
2782            editor
2783        });
2784
2785        let _subscriptions = vec![
2786            cx.observe(&conversation, |_, _, cx| cx.notify()),
2787            cx.subscribe(&conversation, Self::handle_conversation_event),
2788            cx.subscribe(&editor, Self::handle_editor_event),
2789            cx.subscribe(&workspace, Self::handle_workspace_event),
2790        ];
2791
2792        let mut this = Self {
2793            conversation,
2794            editor,
2795            blocks: Default::default(),
2796            scroll_position: None,
2797            flap_ids: Default::default(),
2798            fs,
2799            workspace: workspace.downgrade(),
2800            _subscriptions,
2801        };
2802        this.update_recent_editors(cx);
2803        this.update_message_headers(cx);
2804        this
2805    }
2806
2807    fn assist(&mut self, _: &Assist, cx: &mut ViewContext<Self>) {
2808        let cursors = self.cursors(cx);
2809
2810        let user_messages = self.conversation.update(cx, |conversation, cx| {
2811            let selected_messages = conversation
2812                .messages_for_offsets(cursors, cx)
2813                .into_iter()
2814                .map(|message| message.id)
2815                .collect();
2816            conversation.assist(selected_messages, cx)
2817        });
2818        let new_selections = user_messages
2819            .iter()
2820            .map(|message| {
2821                let cursor = message
2822                    .start
2823                    .to_offset(self.conversation.read(cx).buffer.read(cx));
2824                cursor..cursor
2825            })
2826            .collect::<Vec<_>>();
2827        if !new_selections.is_empty() {
2828            self.editor.update(cx, |editor, cx| {
2829                editor.change_selections(
2830                    Some(Autoscroll::Strategy(AutoscrollStrategy::Fit)),
2831                    cx,
2832                    |selections| selections.select_ranges(new_selections),
2833                );
2834            });
2835            // Avoid scrolling to the new cursor position so the assistant's output is stable.
2836            cx.defer(|this, _| this.scroll_position = None);
2837        }
2838    }
2839
2840    fn cancel_last_assist(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
2841        if !self
2842            .conversation
2843            .update(cx, |conversation, _| conversation.cancel_last_assist())
2844        {
2845            cx.propagate();
2846        }
2847    }
2848
2849    fn cycle_message_role(&mut self, _: &CycleMessageRole, cx: &mut ViewContext<Self>) {
2850        let cursors = self.cursors(cx);
2851        self.conversation.update(cx, |conversation, cx| {
2852            let messages = conversation
2853                .messages_for_offsets(cursors, cx)
2854                .into_iter()
2855                .map(|message| message.id)
2856                .collect();
2857            conversation.cycle_message_roles(messages, cx)
2858        });
2859    }
2860
2861    fn cursors(&self, cx: &AppContext) -> Vec<usize> {
2862        let selections = self.editor.read(cx).selections.all::<usize>(cx);
2863        selections
2864            .into_iter()
2865            .map(|selection| selection.head())
2866            .collect()
2867    }
2868
2869    fn handle_conversation_event(
2870        &mut self,
2871        _: Model<Conversation>,
2872        event: &ConversationEvent,
2873        cx: &mut ViewContext<Self>,
2874    ) {
2875        match event {
2876            ConversationEvent::MessagesEdited => {
2877                self.update_message_headers(cx);
2878                self.conversation.update(cx, |conversation, cx| {
2879                    conversation.save(Some(Duration::from_millis(500)), self.fs.clone(), cx);
2880                });
2881            }
2882            ConversationEvent::EditSuggestionsChanged => {
2883                self.editor.update(cx, |editor, cx| {
2884                    let buffer = editor.buffer().read(cx).snapshot(cx);
2885                    let excerpt_id = *buffer.as_singleton().unwrap().0;
2886                    let conversation = self.conversation.read(cx);
2887                    let highlighted_rows = conversation
2888                        .edit_suggestions
2889                        .iter()
2890                        .map(|suggestion| {
2891                            let start = buffer
2892                                .anchor_in_excerpt(excerpt_id, suggestion.source_range.start)
2893                                .unwrap();
2894                            let end = buffer
2895                                .anchor_in_excerpt(excerpt_id, suggestion.source_range.end)
2896                                .unwrap();
2897                            start..=end
2898                        })
2899                        .collect::<Vec<_>>();
2900
2901                    editor.clear_row_highlights::<EditSuggestion>();
2902                    for range in highlighted_rows {
2903                        editor.highlight_rows::<EditSuggestion>(
2904                            range,
2905                            Some(
2906                                cx.theme()
2907                                    .colors()
2908                                    .editor_document_highlight_read_background,
2909                            ),
2910                            false,
2911                            cx,
2912                        );
2913                    }
2914                });
2915            }
2916            ConversationEvent::SummaryChanged => {
2917                cx.emit(ConversationEditorEvent::TabContentChanged);
2918                self.conversation.update(cx, |conversation, cx| {
2919                    conversation.save(None, self.fs.clone(), cx);
2920                });
2921            }
2922            ConversationEvent::StreamedCompletion => {
2923                self.editor.update(cx, |editor, cx| {
2924                    if let Some(scroll_position) = self.scroll_position {
2925                        let snapshot = editor.snapshot(cx);
2926                        let cursor_point = scroll_position.cursor.to_display_point(&snapshot);
2927                        let scroll_top =
2928                            cursor_point.row().as_f32() - scroll_position.offset_before_cursor.y;
2929                        editor.set_scroll_position(
2930                            point(scroll_position.offset_before_cursor.x, scroll_top),
2931                            cx,
2932                        );
2933                    }
2934                });
2935            }
2936            ConversationEvent::SlashCommandsChanged => {
2937                self.editor.update(cx, |editor, cx| {
2938                    let buffer = editor.buffer().read(cx).snapshot(cx);
2939                    let excerpt_id = *buffer.as_singleton().unwrap().0;
2940                    let conversation = self.conversation.read(cx);
2941                    let colors = cx.theme().colors();
2942                    let highlighted_rows = conversation
2943                        .slash_command_calls
2944                        .iter()
2945                        .map(|call| {
2946                            let start = call.source_range.start;
2947                            let end = if let Some(output) = &call.output_range {
2948                                output.end
2949                            } else {
2950                                call.source_range.end
2951                            };
2952                            let start = buffer.anchor_in_excerpt(excerpt_id, start).unwrap();
2953                            let end = buffer.anchor_in_excerpt(excerpt_id, end).unwrap();
2954                            (
2955                                start..=end,
2956                                Some(colors.editor_document_highlight_read_background),
2957                            )
2958                        })
2959                        .collect::<Vec<_>>();
2960
2961                    editor.clear_row_highlights::<SlashCommandCall>();
2962                    for (range, color) in highlighted_rows {
2963                        editor.highlight_rows::<SlashCommandCall>(range, color, false, cx);
2964                    }
2965                });
2966            }
2967            ConversationEvent::SlashCommandOutputAdded(range) => {
2968                self.editor.update(cx, |editor, cx| {
2969                    let buffer = editor.buffer().read(cx).snapshot(cx);
2970                    let excerpt_id = *buffer.as_singleton().unwrap().0;
2971                    let start = buffer.anchor_in_excerpt(excerpt_id, range.start).unwrap();
2972                    let end = buffer.anchor_in_excerpt(excerpt_id, range.end).unwrap();
2973                    let buffer_row = MultiBufferRow(start.to_point(&buffer).row);
2974
2975                    let flap_id = editor
2976                        .insert_flaps(
2977                            [Flap::new(
2978                                start..end,
2979                                FoldPlaceholder {
2980                                    render: Arc::new(|_, _, _| Empty.into_any()),
2981                                    constrain_width: false,
2982                                },
2983                                render_slash_command_output_toggle,
2984                                render_slash_command_output_trailer,
2985                            )],
2986                            cx,
2987                        )
2988                        .into_iter()
2989                        .next()
2990                        .unwrap();
2991                    self.flap_ids.insert(range.clone(), flap_id);
2992                    editor.fold_at(&FoldAt { buffer_row }, cx);
2993                });
2994            }
2995            ConversationEvent::SlashCommandOutputRemoved(range) => {
2996                if let Some(flap_id) = self.flap_ids.remove(range) {
2997                    self.editor.update(cx, |editor, cx| {
2998                        editor.remove_flaps([flap_id], cx);
2999                    });
3000                }
3001            }
3002        }
3003    }
3004
3005    fn handle_editor_event(
3006        &mut self,
3007        _: View<Editor>,
3008        event: &EditorEvent,
3009        cx: &mut ViewContext<Self>,
3010    ) {
3011        match event {
3012            EditorEvent::ScrollPositionChanged { autoscroll, .. } => {
3013                let cursor_scroll_position = self.cursor_scroll_position(cx);
3014                if *autoscroll {
3015                    self.scroll_position = cursor_scroll_position;
3016                } else if self.scroll_position != cursor_scroll_position {
3017                    self.scroll_position = None;
3018                }
3019            }
3020            EditorEvent::SelectionsChanged { .. } => {
3021                self.scroll_position = self.cursor_scroll_position(cx);
3022            }
3023            _ => {}
3024        }
3025    }
3026
3027    fn handle_workspace_event(
3028        &mut self,
3029        _: View<Workspace>,
3030        event: &WorkspaceEvent,
3031        cx: &mut ViewContext<Self>,
3032    ) {
3033        match event {
3034            WorkspaceEvent::ActiveItemChanged
3035            | WorkspaceEvent::ItemAdded
3036            | WorkspaceEvent::ItemRemoved
3037            | WorkspaceEvent::PaneAdded(_)
3038            | WorkspaceEvent::PaneRemoved => self.update_recent_editors(cx),
3039            _ => {}
3040        }
3041    }
3042
3043    fn update_recent_editors(&mut self, cx: &mut ViewContext<ConversationEditor>) {
3044        let Some(workspace) = self.workspace.upgrade() else {
3045            return;
3046        };
3047
3048        let mut timestamps_by_entity_id = HashMap::default();
3049        for pane in workspace.read(cx).panes() {
3050            let pane = pane.read(cx);
3051            for entry in pane.activation_history() {
3052                timestamps_by_entity_id.insert(entry.entity_id, entry.timestamp);
3053            }
3054        }
3055
3056        let mut timestamps_by_buffer = HashMap::default();
3057        for editor in workspace.read(cx).items_of_type::<Editor>(cx) {
3058            let Some(buffer) = editor.read(cx).buffer().read(cx).as_singleton() else {
3059                continue;
3060            };
3061
3062            let new_timestamp = timestamps_by_entity_id
3063                .get(&editor.entity_id())
3064                .copied()
3065                .unwrap_or_default();
3066            let timestamp = timestamps_by_buffer.entry(buffer).or_insert(new_timestamp);
3067            *timestamp = cmp::max(*timestamp, new_timestamp);
3068        }
3069
3070        let mut recent_buffers = timestamps_by_buffer.into_iter().collect::<Vec<_>>();
3071        recent_buffers.sort_unstable_by_key(|(_, timestamp)| *timestamp);
3072        if recent_buffers.len() > MAX_RECENT_BUFFERS {
3073            let excess = recent_buffers.len() - MAX_RECENT_BUFFERS;
3074            recent_buffers.drain(..excess);
3075        }
3076
3077        self.conversation.update(cx, |conversation, cx| {
3078            conversation
3079                .set_recent_buffers(recent_buffers.into_iter().map(|(buffer, _)| buffer), cx);
3080        });
3081    }
3082
3083    fn cursor_scroll_position(&self, cx: &mut ViewContext<Self>) -> Option<ScrollPosition> {
3084        self.editor.update(cx, |editor, cx| {
3085            let snapshot = editor.snapshot(cx);
3086            let cursor = editor.selections.newest_anchor().head();
3087            let cursor_row = cursor
3088                .to_display_point(&snapshot.display_snapshot)
3089                .row()
3090                .as_f32();
3091            let scroll_position = editor
3092                .scroll_manager
3093                .anchor()
3094                .scroll_position(&snapshot.display_snapshot);
3095
3096            let scroll_bottom = scroll_position.y + editor.visible_line_count().unwrap_or(0.);
3097            if (scroll_position.y..scroll_bottom).contains(&cursor_row) {
3098                Some(ScrollPosition {
3099                    cursor,
3100                    offset_before_cursor: point(scroll_position.x, cursor_row - scroll_position.y),
3101                })
3102            } else {
3103                None
3104            }
3105        })
3106    }
3107
3108    fn update_message_headers(&mut self, cx: &mut ViewContext<Self>) {
3109        let project = self
3110            .workspace
3111            .update(cx, |workspace, _cx| workspace.project().downgrade())
3112            .unwrap();
3113
3114        self.editor.update(cx, |editor, cx| {
3115            let buffer = editor.buffer().read(cx).snapshot(cx);
3116            let excerpt_id = *buffer.as_singleton().unwrap().0;
3117            let old_blocks = std::mem::take(&mut self.blocks);
3118            let new_blocks = self
3119                .conversation
3120                .read(cx)
3121                .messages(cx)
3122                .enumerate()
3123                .map(|(ix, message)| BlockProperties {
3124                    position: buffer
3125                        .anchor_in_excerpt(excerpt_id, message.anchor)
3126                        .unwrap(),
3127                    height: 2,
3128                    style: BlockStyle::Sticky,
3129                    render: Box::new({
3130                        let fs = self.fs.clone();
3131                        let project = project.clone();
3132                        let conversation = self.conversation.clone();
3133                        move |cx| {
3134                            let message_id = message.id;
3135                            let sender = ButtonLike::new("role")
3136                                .style(ButtonStyle::Filled)
3137                                .child(match message.role {
3138                                    Role::User => Label::new("You").color(Color::Default),
3139                                    Role::Assistant => Label::new("Assistant").color(Color::Info),
3140                                    Role::System => Label::new("System").color(Color::Warning),
3141                                })
3142                                .tooltip(|cx| {
3143                                    Tooltip::with_meta(
3144                                        "Toggle message role",
3145                                        None,
3146                                        "Available roles: You (User), Assistant, System",
3147                                        cx,
3148                                    )
3149                                })
3150                                .on_click({
3151                                    let conversation = conversation.clone();
3152                                    move |_, cx| {
3153                                        conversation.update(cx, |conversation, cx| {
3154                                            conversation.cycle_message_roles(
3155                                                HashSet::from_iter(Some(message_id)),
3156                                                cx,
3157                                            )
3158                                        })
3159                                    }
3160                                });
3161
3162                            h_flex()
3163                                .id(("message_header", message_id.0))
3164                                .pl(cx.gutter_dimensions.width)
3165                                .h_11()
3166                                .w_full()
3167                                .relative()
3168                                .gap_1()
3169                                .child(sender)
3170                                .children(
3171                                    if let MessageStatus::Error(error) = message.status.clone() {
3172                                        Some(
3173                                            div()
3174                                                .id("error")
3175                                                .tooltip(move |cx| Tooltip::text(error.clone(), cx))
3176                                                .child(Icon::new(IconName::XCircle)),
3177                                        )
3178                                    } else {
3179                                        None
3180                                    },
3181                                )
3182                                .children((ix == 0).then(|| {
3183                                    div()
3184                                        .h_flex()
3185                                        .flex_1()
3186                                        .justify_end()
3187                                        .pr_4()
3188                                        .gap_1()
3189                                        .child(
3190                                            IconButton::new("include_file", IconName::File)
3191                                                .icon_size(IconSize::Small)
3192                                                .selected(
3193                                                    conversation
3194                                                        .read(cx)
3195                                                        .ambient_context
3196                                                        .recent_buffers
3197                                                        .enabled,
3198                                                )
3199                                                .on_click({
3200                                                    let conversation = conversation.downgrade();
3201                                                    move |_, cx| {
3202                                                        conversation
3203                                                            .update(cx, |conversation, cx| {
3204                                                                conversation
3205                                                                    .toggle_recent_buffers(cx);
3206                                                            })
3207                                                            .ok();
3208                                                    }
3209                                                })
3210                                                .tooltip(|cx| {
3211                                                    Tooltip::text("Include Open Files", cx)
3212                                                }),
3213                                        )
3214                                        .child(
3215                                            IconButton::new(
3216                                                "include_current_project",
3217                                                IconName::FileTree,
3218                                            )
3219                                            .icon_size(IconSize::Small)
3220                                            .selected(
3221                                                conversation
3222                                                    .read(cx)
3223                                                    .ambient_context
3224                                                    .current_project
3225                                                    .enabled,
3226                                            )
3227                                            .on_click({
3228                                                let fs = fs.clone();
3229                                                let project = project.clone();
3230                                                let conversation = conversation.downgrade();
3231                                                move |_, cx| {
3232                                                    let fs = fs.clone();
3233                                                    let project = project.clone();
3234                                                    conversation
3235                                                        .update(cx, |conversation, cx| {
3236                                                            conversation
3237                                                                .toggle_current_project_context(
3238                                                                    fs, project, cx,
3239                                                                );
3240                                                        })
3241                                                        .ok();
3242                                                }
3243                                            })
3244                                            .tooltip(
3245                                                |cx| Tooltip::text("Include Current Project", cx),
3246                                            ),
3247                                        )
3248                                        .into_any()
3249                                }))
3250                                .into_any_element()
3251                        }
3252                    }),
3253                    disposition: BlockDisposition::Above,
3254                })
3255                .collect::<Vec<_>>();
3256
3257            editor.remove_blocks(old_blocks, None, cx);
3258            let ids = editor.insert_blocks(new_blocks, None, cx);
3259            self.blocks = HashSet::from_iter(ids);
3260        });
3261    }
3262
3263    fn quote_selection(
3264        workspace: &mut Workspace,
3265        _: &QuoteSelection,
3266        cx: &mut ViewContext<Workspace>,
3267    ) {
3268        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
3269            return;
3270        };
3271        let Some(editor) = workspace
3272            .active_item(cx)
3273            .and_then(|item| item.act_as::<Editor>(cx))
3274        else {
3275            return;
3276        };
3277
3278        let editor = editor.read(cx);
3279        let range = editor.selections.newest::<usize>(cx).range();
3280        let buffer = editor.buffer().read(cx).snapshot(cx);
3281        let start_language = buffer.language_at(range.start);
3282        let end_language = buffer.language_at(range.end);
3283        let language_name = if start_language == end_language {
3284            start_language.map(|language| language.code_fence_block_name())
3285        } else {
3286            None
3287        };
3288        let language_name = language_name.as_deref().unwrap_or("");
3289
3290        let selected_text = buffer.text_for_range(range).collect::<String>();
3291        let text = if selected_text.is_empty() {
3292            None
3293        } else {
3294            Some(if language_name == "markdown" {
3295                selected_text
3296                    .lines()
3297                    .map(|line| format!("> {}", line))
3298                    .collect::<Vec<_>>()
3299                    .join("\n")
3300            } else {
3301                format!("```{language_name}\n{selected_text}\n```")
3302            })
3303        };
3304
3305        // Activate the panel
3306        if !panel.focus_handle(cx).contains_focused(cx) {
3307            workspace.toggle_panel_focus::<AssistantPanel>(cx);
3308        }
3309
3310        if let Some(text) = text {
3311            panel.update(cx, |_, cx| {
3312                // Wait to create a new conversation until the workspace is no longer
3313                // being updated.
3314                cx.defer(move |panel, cx| {
3315                    if let Some(conversation) = panel
3316                        .active_conversation_editor()
3317                        .cloned()
3318                        .or_else(|| panel.new_conversation(cx))
3319                    {
3320                        conversation.update(cx, |conversation, cx| {
3321                            conversation
3322                                .editor
3323                                .update(cx, |editor, cx| editor.insert(&text, cx))
3324                        });
3325                    };
3326                });
3327            });
3328        }
3329    }
3330
3331    fn copy(&mut self, _: &editor::actions::Copy, cx: &mut ViewContext<Self>) {
3332        let editor = self.editor.read(cx);
3333        let conversation = self.conversation.read(cx);
3334        if editor.selections.count() == 1 {
3335            let selection = editor.selections.newest::<usize>(cx);
3336            let mut copied_text = String::new();
3337            let mut spanned_messages = 0;
3338            for message in conversation.messages(cx) {
3339                if message.offset_range.start >= selection.range().end {
3340                    break;
3341                } else if message.offset_range.end >= selection.range().start {
3342                    let range = cmp::max(message.offset_range.start, selection.range().start)
3343                        ..cmp::min(message.offset_range.end, selection.range().end);
3344                    if !range.is_empty() {
3345                        spanned_messages += 1;
3346                        write!(&mut copied_text, "## {}\n\n", message.role).unwrap();
3347                        for chunk in conversation.buffer.read(cx).text_for_range(range) {
3348                            copied_text.push_str(chunk);
3349                        }
3350                        copied_text.push('\n');
3351                    }
3352                }
3353            }
3354
3355            if spanned_messages > 1 {
3356                cx.write_to_clipboard(ClipboardItem::new(copied_text));
3357                return;
3358            }
3359        }
3360
3361        cx.propagate();
3362    }
3363
3364    fn split(&mut self, _: &Split, cx: &mut ViewContext<Self>) {
3365        self.conversation.update(cx, |conversation, cx| {
3366            let selections = self.editor.read(cx).selections.disjoint_anchors();
3367            for selection in selections.as_ref() {
3368                let buffer = self.editor.read(cx).buffer().read(cx).snapshot(cx);
3369                let range = selection
3370                    .map(|endpoint| endpoint.to_offset(&buffer))
3371                    .range();
3372                conversation.split_message(range, cx);
3373            }
3374        });
3375    }
3376
3377    fn apply_edit(&mut self, _: &ApplyEdit, cx: &mut ViewContext<Self>) {
3378        struct Edit {
3379            old_text: String,
3380            new_text: String,
3381        }
3382
3383        let conversation = self.conversation.read(cx);
3384        let conversation_buffer = conversation.buffer.read(cx);
3385        let conversation_buffer_snapshot = conversation_buffer.snapshot();
3386
3387        let selections = self.editor.read(cx).selections.disjoint_anchors();
3388        let mut selections = selections.iter().peekable();
3389        let selected_suggestions = conversation.edit_suggestions.iter().filter(|suggestion| {
3390            while let Some(selection) = selections.peek() {
3391                if selection
3392                    .end
3393                    .text_anchor
3394                    .cmp(&suggestion.source_range.start, conversation_buffer)
3395                    .is_lt()
3396                {
3397                    selections.next();
3398                    continue;
3399                }
3400                if selection
3401                    .start
3402                    .text_anchor
3403                    .cmp(&suggestion.source_range.end, conversation_buffer)
3404                    .is_gt()
3405                {
3406                    break;
3407                }
3408                return true;
3409            }
3410            false
3411        });
3412
3413        let mut suggestions_by_buffer =
3414            HashMap::<Model<Buffer>, (BufferSnapshot, Vec<Edit>)>::default();
3415        for suggestion in selected_suggestions {
3416            let offset = suggestion.source_range.start.to_offset(conversation_buffer);
3417            if let Some(message) = conversation.message_for_offset(offset, cx) {
3418                if let Some(buffer) = message
3419                    .ambient_context
3420                    .recent_buffers
3421                    .source_buffers
3422                    .iter()
3423                    .find(|source_buffer| {
3424                        source_buffer.full_path.as_ref() == Some(&suggestion.full_path)
3425                    })
3426                {
3427                    if let Some(buffer) = buffer.model.upgrade() {
3428                        let (_, edits) = suggestions_by_buffer
3429                            .entry(buffer.clone())
3430                            .or_insert_with(|| (buffer.read(cx).snapshot(), Vec::new()));
3431
3432                        let mut lines = conversation_buffer_snapshot
3433                            .as_rope()
3434                            .chunks_in_range(
3435                                suggestion
3436                                    .source_range
3437                                    .to_offset(&conversation_buffer_snapshot),
3438                            )
3439                            .lines();
3440                        if let Some(suggestion) = parse_next_edit_suggestion(&mut lines) {
3441                            let old_text = conversation_buffer_snapshot
3442                                .text_for_range(suggestion.old_text_range)
3443                                .collect();
3444                            let new_text = conversation_buffer_snapshot
3445                                .text_for_range(suggestion.new_text_range)
3446                                .collect();
3447                            edits.push(Edit { old_text, new_text });
3448                        }
3449                    }
3450                }
3451            }
3452        }
3453
3454        cx.spawn(|this, mut cx| async move {
3455            let edits_by_buffer = cx
3456                .background_executor()
3457                .spawn(async move {
3458                    let mut result = HashMap::default();
3459                    for (buffer, (snapshot, suggestions)) in suggestions_by_buffer {
3460                        let edits =
3461                            result
3462                                .entry(buffer)
3463                                .or_insert(Vec::<(Range<language::Anchor>, _)>::new());
3464                        for suggestion in suggestions {
3465                            if let Some(range) =
3466                                fuzzy_search_lines(snapshot.as_rope(), &suggestion.old_text)
3467                            {
3468                                let edit_start = snapshot.anchor_after(range.start);
3469                                let edit_end = snapshot.anchor_before(range.end);
3470                                if let Err(ix) = edits.binary_search_by(|(range, _)| {
3471                                    range.start.cmp(&edit_start, &snapshot)
3472                                }) {
3473                                    edits.insert(
3474                                        ix,
3475                                        (edit_start..edit_end, suggestion.new_text.clone()),
3476                                    );
3477                                }
3478                            } else {
3479                                log::info!(
3480                                    "assistant edit did not match any text in buffer {:?}",
3481                                    &suggestion.old_text
3482                                );
3483                            }
3484                        }
3485                    }
3486                    result
3487                })
3488                .await;
3489
3490            let mut project_transaction = ProjectTransaction::default();
3491            let (editor, workspace, title) = this.update(&mut cx, |this, cx| {
3492                for (buffer_handle, edits) in edits_by_buffer {
3493                    buffer_handle.update(cx, |buffer, cx| {
3494                        buffer.start_transaction();
3495                        buffer.edit(
3496                            edits,
3497                            Some(AutoindentMode::Block {
3498                                original_indent_columns: Vec::new(),
3499                            }),
3500                            cx,
3501                        );
3502                        buffer.end_transaction(cx);
3503                        if let Some(transaction) = buffer.finalize_last_transaction() {
3504                            project_transaction
3505                                .0
3506                                .insert(buffer_handle.clone(), transaction.clone());
3507                        }
3508                    });
3509                }
3510
3511                (
3512                    this.editor.downgrade(),
3513                    this.workspace.clone(),
3514                    this.title(cx),
3515                )
3516            })?;
3517
3518            Editor::open_project_transaction(
3519                &editor,
3520                workspace,
3521                project_transaction,
3522                format!("Edits from {}", title),
3523                cx,
3524            )
3525            .await
3526        })
3527        .detach_and_log_err(cx);
3528    }
3529
3530    fn save(&mut self, _: &Save, cx: &mut ViewContext<Self>) {
3531        self.conversation.update(cx, |conversation, cx| {
3532            conversation.save(None, self.fs.clone(), cx)
3533        });
3534    }
3535
3536    fn title(&self, cx: &AppContext) -> String {
3537        self.conversation
3538            .read(cx)
3539            .summary
3540            .as_ref()
3541            .map(|summary| summary.text.clone())
3542            .unwrap_or_else(|| "New Context".into())
3543    }
3544}
3545
3546impl EventEmitter<ConversationEditorEvent> for ConversationEditor {}
3547
3548impl Render for ConversationEditor {
3549    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3550        div()
3551            .key_context("ConversationEditor")
3552            .capture_action(cx.listener(ConversationEditor::cancel_last_assist))
3553            .capture_action(cx.listener(ConversationEditor::save))
3554            .capture_action(cx.listener(ConversationEditor::copy))
3555            .capture_action(cx.listener(ConversationEditor::cycle_message_role))
3556            .on_action(cx.listener(ConversationEditor::assist))
3557            .on_action(cx.listener(ConversationEditor::split))
3558            .on_action(cx.listener(ConversationEditor::apply_edit))
3559            .size_full()
3560            .v_flex()
3561            .child(
3562                div()
3563                    .flex_grow()
3564                    .bg(cx.theme().colors().editor_background)
3565                    .child(self.editor.clone()),
3566            )
3567    }
3568}
3569
3570impl FocusableView for ConversationEditor {
3571    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3572        self.editor.focus_handle(cx)
3573    }
3574}
3575
3576#[derive(Clone, Debug)]
3577struct MessageAnchor {
3578    id: MessageId,
3579    start: language::Anchor,
3580}
3581
3582#[derive(Clone, Debug)]
3583pub struct Message {
3584    offset_range: Range<usize>,
3585    index_range: Range<usize>,
3586    id: MessageId,
3587    anchor: language::Anchor,
3588    role: Role,
3589    status: MessageStatus,
3590    slash_command_ranges: Vec<Range<usize>>,
3591    ambient_context: AmbientContextSnapshot,
3592}
3593
3594impl Message {
3595    fn to_request_message(&self, buffer: &Buffer) -> LanguageModelRequestMessage {
3596        let mut content = text_in_range_omitting_ranges(
3597            buffer.as_rope(),
3598            self.offset_range.clone(),
3599            &self.slash_command_ranges,
3600        );
3601        content.truncate(content.trim_end().len());
3602        LanguageModelRequestMessage {
3603            role: self.role,
3604            content,
3605        }
3606    }
3607}
3608
3609enum InlineAssistantEvent {
3610    Confirmed {
3611        prompt: String,
3612        include_conversation: bool,
3613    },
3614    Canceled,
3615    Dismissed,
3616    IncludeConversationToggled {
3617        include_conversation: bool,
3618    },
3619}
3620
3621struct InlineAssistant {
3622    id: usize,
3623    prompt_editor: View<Editor>,
3624    confirmed: bool,
3625    show_include_conversation: bool,
3626    include_conversation: bool,
3627    measurements: Arc<Mutex<BlockMeasurements>>,
3628    prompt_history: VecDeque<String>,
3629    prompt_history_ix: Option<usize>,
3630    pending_prompt: String,
3631    codegen: Model<Codegen>,
3632    _subscriptions: Vec<Subscription>,
3633}
3634
3635impl EventEmitter<InlineAssistantEvent> for InlineAssistant {}
3636
3637impl Render for InlineAssistant {
3638    fn render(&mut self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3639        let measurements = *self.measurements.lock();
3640        h_flex()
3641            .w_full()
3642            .py_2()
3643            .border_y_1()
3644            .border_color(cx.theme().colors().border)
3645            .on_action(cx.listener(Self::confirm))
3646            .on_action(cx.listener(Self::cancel))
3647            .on_action(cx.listener(Self::toggle_include_conversation))
3648            .on_action(cx.listener(Self::move_up))
3649            .on_action(cx.listener(Self::move_down))
3650            .child(
3651                h_flex()
3652                    .justify_center()
3653                    .w(measurements.gutter_width)
3654                    .children(self.show_include_conversation.then(|| {
3655                        IconButton::new("include_conversation", IconName::Ai)
3656                            .on_click(cx.listener(|this, _, cx| {
3657                                this.toggle_include_conversation(&ToggleIncludeConversation, cx)
3658                            }))
3659                            .selected(self.include_conversation)
3660                            .tooltip(|cx| {
3661                                Tooltip::for_action(
3662                                    "Include Conversation",
3663                                    &ToggleIncludeConversation,
3664                                    cx,
3665                                )
3666                            })
3667                    }))
3668                    .children(if let Some(error) = self.codegen.read(cx).error() {
3669                        let error_message = SharedString::from(error.to_string());
3670                        Some(
3671                            div()
3672                                .id("error")
3673                                .tooltip(move |cx| Tooltip::text(error_message.clone(), cx))
3674                                .child(Icon::new(IconName::XCircle).color(Color::Error)),
3675                        )
3676                    } else {
3677                        None
3678                    }),
3679            )
3680            .child(
3681                h_flex()
3682                    .w_full()
3683                    .ml(measurements.anchor_x - measurements.gutter_width)
3684                    .child(self.render_prompt_editor(cx)),
3685            )
3686    }
3687}
3688
3689impl FocusableView for InlineAssistant {
3690    fn focus_handle(&self, cx: &AppContext) -> FocusHandle {
3691        self.prompt_editor.focus_handle(cx)
3692    }
3693}
3694
3695impl InlineAssistant {
3696    #[allow(clippy::too_many_arguments)]
3697    fn new(
3698        id: usize,
3699        measurements: Arc<Mutex<BlockMeasurements>>,
3700        show_include_conversation: bool,
3701        include_conversation: bool,
3702        prompt_history: VecDeque<String>,
3703        codegen: Model<Codegen>,
3704        cx: &mut ViewContext<Self>,
3705    ) -> Self {
3706        let prompt_editor = cx.new_view(|cx| {
3707            let mut editor = Editor::single_line(cx);
3708            let placeholder = match codegen.read(cx).kind() {
3709                CodegenKind::Transform { .. } => "Enter transformation prompt…",
3710                CodegenKind::Generate { .. } => "Enter generation prompt…",
3711            };
3712            editor.set_placeholder_text(placeholder, cx);
3713            editor
3714        });
3715        cx.focus_view(&prompt_editor);
3716
3717        let subscriptions = vec![
3718            cx.observe(&codegen, Self::handle_codegen_changed),
3719            cx.subscribe(&prompt_editor, Self::handle_prompt_editor_events),
3720        ];
3721
3722        Self {
3723            id,
3724            prompt_editor,
3725            confirmed: false,
3726            show_include_conversation,
3727            include_conversation,
3728            measurements,
3729            prompt_history,
3730            prompt_history_ix: None,
3731            pending_prompt: String::new(),
3732            codegen,
3733            _subscriptions: subscriptions,
3734        }
3735    }
3736
3737    fn handle_prompt_editor_events(
3738        &mut self,
3739        _: View<Editor>,
3740        event: &EditorEvent,
3741        cx: &mut ViewContext<Self>,
3742    ) {
3743        if let EditorEvent::Edited = event {
3744            self.pending_prompt = self.prompt_editor.read(cx).text(cx);
3745            cx.notify();
3746        }
3747    }
3748
3749    fn handle_codegen_changed(&mut self, _: Model<Codegen>, cx: &mut ViewContext<Self>) {
3750        let is_read_only = !self.codegen.read(cx).idle();
3751        self.prompt_editor.update(cx, |editor, cx| {
3752            let was_read_only = editor.read_only(cx);
3753            if was_read_only != is_read_only {
3754                if is_read_only {
3755                    editor.set_read_only(true);
3756                } else {
3757                    self.confirmed = false;
3758                    editor.set_read_only(false);
3759                }
3760            }
3761        });
3762        cx.notify();
3763    }
3764
3765    fn cancel(&mut self, _: &editor::actions::Cancel, cx: &mut ViewContext<Self>) {
3766        cx.emit(InlineAssistantEvent::Canceled);
3767    }
3768
3769    fn confirm(&mut self, _: &menu::Confirm, cx: &mut ViewContext<Self>) {
3770        if self.confirmed {
3771            cx.emit(InlineAssistantEvent::Dismissed);
3772        } else {
3773            let prompt = self.prompt_editor.read(cx).text(cx);
3774            self.prompt_editor
3775                .update(cx, |editor, _cx| editor.set_read_only(true));
3776            cx.emit(InlineAssistantEvent::Confirmed {
3777                prompt,
3778                include_conversation: self.include_conversation,
3779            });
3780            self.confirmed = true;
3781            cx.notify();
3782        }
3783    }
3784
3785    fn toggle_include_conversation(
3786        &mut self,
3787        _: &ToggleIncludeConversation,
3788        cx: &mut ViewContext<Self>,
3789    ) {
3790        self.include_conversation = !self.include_conversation;
3791        cx.emit(InlineAssistantEvent::IncludeConversationToggled {
3792            include_conversation: self.include_conversation,
3793        });
3794        cx.notify();
3795    }
3796
3797    fn move_up(&mut self, _: &MoveUp, cx: &mut ViewContext<Self>) {
3798        if let Some(ix) = self.prompt_history_ix {
3799            if ix > 0 {
3800                self.prompt_history_ix = Some(ix - 1);
3801                let prompt = self.prompt_history[ix - 1].clone();
3802                self.set_prompt(&prompt, cx);
3803            }
3804        } else if !self.prompt_history.is_empty() {
3805            self.prompt_history_ix = Some(self.prompt_history.len() - 1);
3806            let prompt = self.prompt_history[self.prompt_history.len() - 1].clone();
3807            self.set_prompt(&prompt, cx);
3808        }
3809    }
3810
3811    fn move_down(&mut self, _: &MoveDown, cx: &mut ViewContext<Self>) {
3812        if let Some(ix) = self.prompt_history_ix {
3813            if ix < self.prompt_history.len() - 1 {
3814                self.prompt_history_ix = Some(ix + 1);
3815                let prompt = self.prompt_history[ix + 1].clone();
3816                self.set_prompt(&prompt, cx);
3817            } else {
3818                self.prompt_history_ix = None;
3819                let pending_prompt = self.pending_prompt.clone();
3820                self.set_prompt(&pending_prompt, cx);
3821            }
3822        }
3823    }
3824
3825    fn set_prompt(&mut self, prompt: &str, cx: &mut ViewContext<Self>) {
3826        self.prompt_editor.update(cx, |editor, cx| {
3827            editor.buffer().update(cx, |buffer, cx| {
3828                let len = buffer.len(cx);
3829                buffer.edit([(0..len, prompt)], None, cx);
3830            });
3831        });
3832    }
3833
3834    fn render_prompt_editor(&self, cx: &mut ViewContext<Self>) -> impl IntoElement {
3835        let settings = ThemeSettings::get_global(cx);
3836        let text_style = TextStyle {
3837            color: if self.prompt_editor.read(cx).read_only(cx) {
3838                cx.theme().colors().text_disabled
3839            } else {
3840                cx.theme().colors().text
3841            },
3842            font_family: settings.ui_font.family.clone(),
3843            font_features: settings.ui_font.features.clone(),
3844            font_size: rems(0.875).into(),
3845            font_weight: FontWeight::NORMAL,
3846            font_style: FontStyle::Normal,
3847            line_height: relative(1.3),
3848            background_color: None,
3849            underline: None,
3850            strikethrough: None,
3851            white_space: WhiteSpace::Normal,
3852        };
3853        EditorElement::new(
3854            &self.prompt_editor,
3855            EditorStyle {
3856                background: cx.theme().colors().editor_background,
3857                local_player: cx.theme().players().local(),
3858                text: text_style,
3859                ..Default::default()
3860            },
3861        )
3862    }
3863}
3864
3865// This wouldn't need to exist if we could pass parameters when rendering child views.
3866#[derive(Copy, Clone, Default)]
3867struct BlockMeasurements {
3868    anchor_x: Pixels,
3869    gutter_width: Pixels,
3870}
3871
3872struct PendingInlineAssist {
3873    editor: WeakView<Editor>,
3874    inline_assistant: Option<(BlockId, View<InlineAssistant>)>,
3875    codegen: Model<Codegen>,
3876    _subscriptions: Vec<Subscription>,
3877    project: WeakModel<Project>,
3878}
3879
3880type ToggleFold = Arc<dyn Fn(bool, &mut WindowContext) + Send + Sync>;
3881
3882fn render_slash_command_output_toggle(
3883    row: MultiBufferRow,
3884    is_folded: bool,
3885    fold: ToggleFold,
3886    _cx: &mut WindowContext,
3887) -> AnyElement {
3888    IconButton::new(
3889        ("slash-command-output-fold-indicator", row.0),
3890        ui::IconName::ChevronDown,
3891    )
3892    .on_click(move |_e, cx| fold(!is_folded, cx))
3893    .icon_color(ui::Color::Muted)
3894    .icon_size(ui::IconSize::Small)
3895    .selected(is_folded)
3896    .selected_icon(ui::IconName::ChevronRight)
3897    .size(ui::ButtonSize::None)
3898    .into_any_element()
3899}
3900
3901fn render_slash_command_output_trailer(
3902    _row: MultiBufferRow,
3903    _is_folded: bool,
3904    _cx: &mut WindowContext,
3905) -> AnyElement {
3906    div().into_any_element()
3907}
3908
3909fn merge_ranges(ranges: &mut Vec<Range<Anchor>>, buffer: &MultiBufferSnapshot) {
3910    ranges.sort_unstable_by(|a, b| {
3911        a.start
3912            .cmp(&b.start, buffer)
3913            .then_with(|| b.end.cmp(&a.end, buffer))
3914    });
3915
3916    let mut ix = 0;
3917    while ix + 1 < ranges.len() {
3918        let b = ranges[ix + 1].clone();
3919        let a = &mut ranges[ix];
3920        if a.end.cmp(&b.start, buffer).is_gt() {
3921            if a.end.cmp(&b.end, buffer).is_lt() {
3922                a.end = b.end;
3923            }
3924            ranges.remove(ix + 1);
3925        } else {
3926            ix += 1;
3927        }
3928    }
3929}
3930
3931fn make_lsp_adapter_delegate(
3932    project: &Model<Project>,
3933    cx: &mut AppContext,
3934) -> Result<Arc<dyn LspAdapterDelegate>> {
3935    project.update(cx, |project, cx| {
3936        // TODO: Find the right worktree.
3937        let worktree = project
3938            .worktrees()
3939            .next()
3940            .ok_or_else(|| anyhow!("no worktrees when constructing ProjectLspAdapterDelegate"))?;
3941        Ok(ProjectLspAdapterDelegate::new(project, &worktree, cx) as Arc<dyn LspAdapterDelegate>)
3942    })
3943}
3944
3945#[cfg(test)]
3946mod tests {
3947    use std::{cell::RefCell, path::Path, rc::Rc};
3948
3949    use super::*;
3950    use crate::{FakeCompletionProvider, MessageId};
3951    use fs::FakeFs;
3952    use gpui::{AppContext, TestAppContext};
3953    use rope::Rope;
3954    use serde_json::json;
3955    use settings::SettingsStore;
3956    use unindent::Unindent;
3957    use util::test::marked_text_ranges;
3958
3959    #[gpui::test]
3960    fn test_inserting_and_removing_messages(cx: &mut AppContext) {
3961        let settings_store = SettingsStore::test(cx);
3962        cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
3963        cx.set_global(settings_store);
3964        init(cx);
3965        let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
3966
3967        let conversation = cx.new_model(|cx| {
3968            Conversation::new(
3969                LanguageModel::default(),
3970                registry,
3971                Default::default(),
3972                None,
3973                None,
3974                cx,
3975            )
3976        });
3977        let buffer = conversation.read(cx).buffer.clone();
3978
3979        let message_1 = conversation.read(cx).message_anchors[0].clone();
3980        assert_eq!(
3981            messages(&conversation, cx),
3982            vec![(message_1.id, Role::User, 0..0)]
3983        );
3984
3985        let message_2 = conversation.update(cx, |conversation, cx| {
3986            conversation
3987                .insert_message_after(message_1.id, Role::Assistant, MessageStatus::Done, cx)
3988                .unwrap()
3989        });
3990        assert_eq!(
3991            messages(&conversation, cx),
3992            vec![
3993                (message_1.id, Role::User, 0..1),
3994                (message_2.id, Role::Assistant, 1..1)
3995            ]
3996        );
3997
3998        buffer.update(cx, |buffer, cx| {
3999            buffer.edit([(0..0, "1"), (1..1, "2")], None, cx)
4000        });
4001        assert_eq!(
4002            messages(&conversation, cx),
4003            vec![
4004                (message_1.id, Role::User, 0..2),
4005                (message_2.id, Role::Assistant, 2..3)
4006            ]
4007        );
4008
4009        let message_3 = conversation.update(cx, |conversation, cx| {
4010            conversation
4011                .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
4012                .unwrap()
4013        });
4014        assert_eq!(
4015            messages(&conversation, cx),
4016            vec![
4017                (message_1.id, Role::User, 0..2),
4018                (message_2.id, Role::Assistant, 2..4),
4019                (message_3.id, Role::User, 4..4)
4020            ]
4021        );
4022
4023        let message_4 = conversation.update(cx, |conversation, cx| {
4024            conversation
4025                .insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
4026                .unwrap()
4027        });
4028        assert_eq!(
4029            messages(&conversation, cx),
4030            vec![
4031                (message_1.id, Role::User, 0..2),
4032                (message_2.id, Role::Assistant, 2..4),
4033                (message_4.id, Role::User, 4..5),
4034                (message_3.id, Role::User, 5..5),
4035            ]
4036        );
4037
4038        buffer.update(cx, |buffer, cx| {
4039            buffer.edit([(4..4, "C"), (5..5, "D")], None, cx)
4040        });
4041        assert_eq!(
4042            messages(&conversation, cx),
4043            vec![
4044                (message_1.id, Role::User, 0..2),
4045                (message_2.id, Role::Assistant, 2..4),
4046                (message_4.id, Role::User, 4..6),
4047                (message_3.id, Role::User, 6..7),
4048            ]
4049        );
4050
4051        // Deleting across message boundaries merges the messages.
4052        buffer.update(cx, |buffer, cx| buffer.edit([(1..4, "")], None, cx));
4053        assert_eq!(
4054            messages(&conversation, cx),
4055            vec![
4056                (message_1.id, Role::User, 0..3),
4057                (message_3.id, Role::User, 3..4),
4058            ]
4059        );
4060
4061        // Undoing the deletion should also undo the merge.
4062        buffer.update(cx, |buffer, cx| buffer.undo(cx));
4063        assert_eq!(
4064            messages(&conversation, cx),
4065            vec![
4066                (message_1.id, Role::User, 0..2),
4067                (message_2.id, Role::Assistant, 2..4),
4068                (message_4.id, Role::User, 4..6),
4069                (message_3.id, Role::User, 6..7),
4070            ]
4071        );
4072
4073        // Redoing the deletion should also redo the merge.
4074        buffer.update(cx, |buffer, cx| buffer.redo(cx));
4075        assert_eq!(
4076            messages(&conversation, cx),
4077            vec![
4078                (message_1.id, Role::User, 0..3),
4079                (message_3.id, Role::User, 3..4),
4080            ]
4081        );
4082
4083        // Ensure we can still insert after a merged message.
4084        let message_5 = conversation.update(cx, |conversation, cx| {
4085            conversation
4086                .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
4087                .unwrap()
4088        });
4089        assert_eq!(
4090            messages(&conversation, cx),
4091            vec![
4092                (message_1.id, Role::User, 0..3),
4093                (message_5.id, Role::System, 3..4),
4094                (message_3.id, Role::User, 4..5)
4095            ]
4096        );
4097    }
4098
4099    #[gpui::test]
4100    fn test_message_splitting(cx: &mut AppContext) {
4101        let settings_store = SettingsStore::test(cx);
4102        cx.set_global(settings_store);
4103        cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
4104        init(cx);
4105        let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
4106
4107        let conversation = cx.new_model(|cx| {
4108            Conversation::new(
4109                LanguageModel::default(),
4110                registry,
4111                Default::default(),
4112                None,
4113                None,
4114                cx,
4115            )
4116        });
4117        let buffer = conversation.read(cx).buffer.clone();
4118
4119        let message_1 = conversation.read(cx).message_anchors[0].clone();
4120        assert_eq!(
4121            messages(&conversation, cx),
4122            vec![(message_1.id, Role::User, 0..0)]
4123        );
4124
4125        buffer.update(cx, |buffer, cx| {
4126            buffer.edit([(0..0, "aaa\nbbb\nccc\nddd\n")], None, cx)
4127        });
4128
4129        let (_, message_2) =
4130            conversation.update(cx, |conversation, cx| conversation.split_message(3..3, cx));
4131        let message_2 = message_2.unwrap();
4132
4133        // We recycle newlines in the middle of a split message
4134        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\nddd\n");
4135        assert_eq!(
4136            messages(&conversation, cx),
4137            vec![
4138                (message_1.id, Role::User, 0..4),
4139                (message_2.id, Role::User, 4..16),
4140            ]
4141        );
4142
4143        let (_, message_3) =
4144            conversation.update(cx, |conversation, cx| conversation.split_message(3..3, cx));
4145        let message_3 = message_3.unwrap();
4146
4147        // We don't recycle newlines at the end of a split message
4148        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
4149        assert_eq!(
4150            messages(&conversation, cx),
4151            vec![
4152                (message_1.id, Role::User, 0..4),
4153                (message_3.id, Role::User, 4..5),
4154                (message_2.id, Role::User, 5..17),
4155            ]
4156        );
4157
4158        let (_, message_4) =
4159            conversation.update(cx, |conversation, cx| conversation.split_message(9..9, cx));
4160        let message_4 = message_4.unwrap();
4161        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\nccc\nddd\n");
4162        assert_eq!(
4163            messages(&conversation, cx),
4164            vec![
4165                (message_1.id, Role::User, 0..4),
4166                (message_3.id, Role::User, 4..5),
4167                (message_2.id, Role::User, 5..9),
4168                (message_4.id, Role::User, 9..17),
4169            ]
4170        );
4171
4172        let (_, message_5) =
4173            conversation.update(cx, |conversation, cx| conversation.split_message(9..9, cx));
4174        let message_5 = message_5.unwrap();
4175        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\nddd\n");
4176        assert_eq!(
4177            messages(&conversation, cx),
4178            vec![
4179                (message_1.id, Role::User, 0..4),
4180                (message_3.id, Role::User, 4..5),
4181                (message_2.id, Role::User, 5..9),
4182                (message_4.id, Role::User, 9..10),
4183                (message_5.id, Role::User, 10..18),
4184            ]
4185        );
4186
4187        let (message_6, message_7) = conversation.update(cx, |conversation, cx| {
4188            conversation.split_message(14..16, cx)
4189        });
4190        let message_6 = message_6.unwrap();
4191        let message_7 = message_7.unwrap();
4192        assert_eq!(buffer.read(cx).text(), "aaa\n\nbbb\n\nccc\ndd\nd\n");
4193        assert_eq!(
4194            messages(&conversation, cx),
4195            vec![
4196                (message_1.id, Role::User, 0..4),
4197                (message_3.id, Role::User, 4..5),
4198                (message_2.id, Role::User, 5..9),
4199                (message_4.id, Role::User, 9..10),
4200                (message_5.id, Role::User, 10..14),
4201                (message_6.id, Role::User, 14..17),
4202                (message_7.id, Role::User, 17..19),
4203            ]
4204        );
4205    }
4206
4207    #[gpui::test]
4208    fn test_messages_for_offsets(cx: &mut AppContext) {
4209        let settings_store = SettingsStore::test(cx);
4210        cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
4211        cx.set_global(settings_store);
4212        init(cx);
4213        let registry = Arc::new(LanguageRegistry::test(cx.background_executor().clone()));
4214        let conversation = cx.new_model(|cx| {
4215            Conversation::new(
4216                LanguageModel::default(),
4217                registry,
4218                Default::default(),
4219                None,
4220                None,
4221                cx,
4222            )
4223        });
4224        let buffer = conversation.read(cx).buffer.clone();
4225
4226        let message_1 = conversation.read(cx).message_anchors[0].clone();
4227        assert_eq!(
4228            messages(&conversation, cx),
4229            vec![(message_1.id, Role::User, 0..0)]
4230        );
4231
4232        buffer.update(cx, |buffer, cx| buffer.edit([(0..0, "aaa")], None, cx));
4233        let message_2 = conversation
4234            .update(cx, |conversation, cx| {
4235                conversation.insert_message_after(message_1.id, Role::User, MessageStatus::Done, cx)
4236            })
4237            .unwrap();
4238        buffer.update(cx, |buffer, cx| buffer.edit([(4..4, "bbb")], None, cx));
4239
4240        let message_3 = conversation
4241            .update(cx, |conversation, cx| {
4242                conversation.insert_message_after(message_2.id, Role::User, MessageStatus::Done, cx)
4243            })
4244            .unwrap();
4245        buffer.update(cx, |buffer, cx| buffer.edit([(8..8, "ccc")], None, cx));
4246
4247        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc");
4248        assert_eq!(
4249            messages(&conversation, cx),
4250            vec![
4251                (message_1.id, Role::User, 0..4),
4252                (message_2.id, Role::User, 4..8),
4253                (message_3.id, Role::User, 8..11)
4254            ]
4255        );
4256
4257        assert_eq!(
4258            message_ids_for_offsets(&conversation, &[0, 4, 9], cx),
4259            [message_1.id, message_2.id, message_3.id]
4260        );
4261        assert_eq!(
4262            message_ids_for_offsets(&conversation, &[0, 1, 11], cx),
4263            [message_1.id, message_3.id]
4264        );
4265
4266        let message_4 = conversation
4267            .update(cx, |conversation, cx| {
4268                conversation.insert_message_after(message_3.id, Role::User, MessageStatus::Done, cx)
4269            })
4270            .unwrap();
4271        assert_eq!(buffer.read(cx).text(), "aaa\nbbb\nccc\n");
4272        assert_eq!(
4273            messages(&conversation, cx),
4274            vec![
4275                (message_1.id, Role::User, 0..4),
4276                (message_2.id, Role::User, 4..8),
4277                (message_3.id, Role::User, 8..12),
4278                (message_4.id, Role::User, 12..12)
4279            ]
4280        );
4281        assert_eq!(
4282            message_ids_for_offsets(&conversation, &[0, 4, 8, 12], cx),
4283            [message_1.id, message_2.id, message_3.id, message_4.id]
4284        );
4285
4286        fn message_ids_for_offsets(
4287            conversation: &Model<Conversation>,
4288            offsets: &[usize],
4289            cx: &AppContext,
4290        ) -> Vec<MessageId> {
4291            conversation
4292                .read(cx)
4293                .messages_for_offsets(offsets.iter().copied(), cx)
4294                .into_iter()
4295                .map(|message| message.id)
4296                .collect()
4297        }
4298    }
4299
4300    #[gpui::test]
4301    async fn test_slash_commands(cx: &mut TestAppContext) {
4302        let settings_store = cx.update(SettingsStore::test);
4303        cx.set_global(settings_store);
4304        cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
4305        cx.update(Project::init_settings);
4306        cx.update(init);
4307        let fs = FakeFs::new(cx.background_executor.clone());
4308
4309        fs.insert_tree(
4310            "/test",
4311            json!({
4312                "src": {
4313                    "lib.rs": "fn one() -> usize { 1 }",
4314                    "main.rs": "
4315                        use crate::one;
4316                        fn main() { one(); }
4317                    ".unindent(),
4318                }
4319            }),
4320        )
4321        .await;
4322
4323        let project = Project::test(fs.clone(), ["/test".as_ref()], cx).await;
4324        let prompt_library = Arc::new(PromptLibrary::default());
4325        let slash_command_registry = SlashCommandRegistry::new();
4326
4327        slash_command_registry
4328            .register_command(file_command::FileSlashCommand::new(project.clone()));
4329        slash_command_registry.register_command(prompt_command::PromptSlashCommand::new(
4330            prompt_library.clone(),
4331        ));
4332
4333        let lsp_adapter_delegate = project.update(cx, |project, cx| {
4334            // TODO: Find the right worktree.
4335            let worktree = project
4336                .worktrees()
4337                .next()
4338                .expect("expected at least one worktree");
4339            ProjectLspAdapterDelegate::new(project, &worktree, cx)
4340        });
4341
4342        let registry = Arc::new(LanguageRegistry::test(cx.executor()));
4343        let conversation = cx.new_model(|cx| {
4344            Conversation::new(
4345                LanguageModel::default(),
4346                registry.clone(),
4347                slash_command_registry,
4348                None,
4349                Some(lsp_adapter_delegate),
4350                cx,
4351            )
4352        });
4353
4354        let output_ranges = Rc::new(RefCell::new(HashSet::default()));
4355        conversation.update(cx, |_, cx| {
4356            cx.subscribe(&conversation, {
4357                let ranges = output_ranges.clone();
4358                move |_, _, event, _| match event {
4359                    ConversationEvent::SlashCommandOutputAdded(range) => {
4360                        ranges.borrow_mut().insert(range.clone());
4361                    }
4362                    ConversationEvent::SlashCommandOutputRemoved(range) => {
4363                        ranges.borrow_mut().remove(range);
4364                    }
4365                    _ => {}
4366                }
4367            })
4368            .detach();
4369        });
4370
4371        let buffer = conversation.read_with(cx, |conversation, _| conversation.buffer.clone());
4372
4373        // Insert a slash command
4374        buffer.update(cx, |buffer, cx| {
4375            buffer.edit([(0..0, "/file src/lib.rs")], None, cx);
4376        });
4377        assert_text_and_output_ranges(
4378            &buffer,
4379            &output_ranges.borrow(),
4380            "
4381            /file src/lib.rs
4382            "
4383            .unindent()
4384            .trim_end(),
4385            cx,
4386        );
4387
4388        // The slash command runs
4389        cx.executor().advance_clock(SLASH_COMMAND_DEBOUNCE);
4390        assert_text_and_output_ranges(
4391            &buffer,
4392            &output_ranges.borrow(),
4393            &"
4394            /file src/lib.rs«
4395            ```src/lib.rs
4396            fn one() -> usize { 1 }
4397            ```»"
4398                .unindent(),
4399            cx,
4400        );
4401
4402        // Edit the slash command
4403        buffer.update(cx, |buffer, cx| {
4404            let edit_offset = buffer.text().find("lib.rs").unwrap();
4405            buffer.edit([(edit_offset..edit_offset + "lib".len(), "main")], None, cx);
4406        });
4407        assert_text_and_output_ranges(
4408            &buffer,
4409            &output_ranges.borrow(),
4410            &"
4411            /file src/main.rs«
4412            ```src/lib.rs
4413            fn one() -> usize { 1 }
4414            ```»"
4415                .unindent(),
4416            cx,
4417        );
4418
4419        cx.executor().advance_clock(SLASH_COMMAND_DEBOUNCE);
4420        assert_text_and_output_ranges(
4421            &buffer,
4422            &output_ranges.borrow(),
4423            &"
4424            /file src/main.rs«
4425            ```src/main.rs
4426            use crate::one;
4427            fn main() { one(); }
4428            ```»"
4429                .unindent(),
4430            cx,
4431        );
4432
4433        // Insert newlines between the slash command and its output
4434        buffer.update(cx, |buffer, cx| {
4435            let edit_offset = buffer.text().find("\n```src/main.rs").unwrap();
4436            buffer.edit([(edit_offset..edit_offset, "\n")], None, cx);
4437        });
4438        assert_text_and_output_ranges(
4439            &buffer,
4440            &output_ranges.borrow(),
4441            &"
4442            /file src/main.rs«
4443
4444            ```src/main.rs
4445            use crate::one;
4446            fn main() { one(); }
4447            ```»"
4448                .unindent(),
4449            cx,
4450        );
4451
4452        cx.executor().advance_clock(SLASH_COMMAND_DEBOUNCE);
4453        assert_text_and_output_ranges(
4454            &buffer,
4455            &output_ranges.borrow(),
4456            &"
4457            /file src/main.rs«
4458            ```src/main.rs
4459            use crate::one;
4460            fn main() { one(); }
4461            ```»"
4462                .unindent(),
4463            cx,
4464        );
4465
4466        // Insert text at the beginning of the output
4467        buffer.update(cx, |buffer, cx| {
4468            let edit_offset = buffer.text().find("```src/main.rs").unwrap();
4469            buffer.edit([(edit_offset..edit_offset, "!")], None, cx);
4470        });
4471        assert_text_and_output_ranges(
4472            &buffer,
4473            &output_ranges.borrow(),
4474            &"
4475            /file src/main.rs«
4476            !```src/main.rs
4477            use crate::one;
4478            fn main() { one(); }
4479            ```»"
4480                .unindent(),
4481            cx,
4482        );
4483
4484        cx.executor().advance_clock(SLASH_COMMAND_DEBOUNCE);
4485        assert_text_and_output_ranges(
4486            &buffer,
4487            &output_ranges.borrow(),
4488            &"
4489            /file src/main.rs«
4490            ```src/main.rs
4491            use crate::one;
4492            fn main() { one(); }
4493            ```»"
4494                .unindent(),
4495            cx,
4496        );
4497
4498        // Slash commands are omitted from completion requests. Only their
4499        // output is included.
4500        let request = conversation.update(cx, |conversation, cx| {
4501            conversation.to_completion_request(cx)
4502        });
4503        assert_eq!(
4504            &request.messages[1..],
4505            &[LanguageModelRequestMessage {
4506                role: Role::User,
4507                content: "
4508                ```src/main.rs
4509                use crate::one;
4510                fn main() { one(); }
4511                ```"
4512                .unindent()
4513            }]
4514        );
4515
4516        buffer.update(cx, |buffer, cx| {
4517            buffer.edit([(0..0, "hello\n")], None, cx);
4518        });
4519        buffer.update(cx, |buffer, cx| {
4520            buffer.edit(
4521                [(buffer.len()..buffer.len(), "\ngoodbye\nfarewell\n")],
4522                None,
4523                cx,
4524            );
4525        });
4526        let request = conversation.update(cx, |conversation, cx| {
4527            conversation.to_completion_request(cx)
4528        });
4529        assert_eq!(
4530            &request.messages[1..],
4531            &[LanguageModelRequestMessage {
4532                role: Role::User,
4533                content: "
4534                hello
4535                ```src/main.rs
4536                use crate::one;
4537                fn main() { one(); }
4538                ```
4539                goodbye
4540                farewell"
4541                    .unindent()
4542            }]
4543        );
4544
4545        #[track_caller]
4546        fn assert_text_and_output_ranges(
4547            buffer: &Model<Buffer>,
4548            ranges: &HashSet<Range<language::Anchor>>,
4549            expected_marked_text: &str,
4550            cx: &mut TestAppContext,
4551        ) {
4552            let (expected_text, expected_ranges) = marked_text_ranges(expected_marked_text, false);
4553            let (actual_text, actual_ranges) = buffer.update(cx, |buffer, _| {
4554                let mut ranges = ranges
4555                    .iter()
4556                    .map(|range| range.to_offset(buffer))
4557                    .collect::<Vec<_>>();
4558                ranges.sort_by_key(|a| a.start);
4559                (buffer.text(), ranges)
4560            });
4561
4562            assert_eq!(actual_text, expected_text);
4563            assert_eq!(actual_ranges, expected_ranges);
4564        }
4565    }
4566
4567    #[test]
4568    fn test_parse_next_edit_suggestion() {
4569        let text = "
4570            some output:
4571
4572            ```edit src/foo.rs
4573                let a = 1;
4574                let b = 2;
4575            ---
4576                let w = 1;
4577                let x = 2;
4578                let y = 3;
4579                let z = 4;
4580            ```
4581
4582            some more output:
4583
4584            ```edit src/foo.rs
4585                let c = 1;
4586            ---
4587            ```
4588
4589            and the conclusion.
4590        "
4591        .unindent();
4592
4593        let rope = Rope::from(text.as_str());
4594        let mut lines = rope.chunks().lines();
4595        let mut suggestions = vec![];
4596        while let Some(suggestion) = parse_next_edit_suggestion(&mut lines) {
4597            suggestions.push((
4598                suggestion.path.clone(),
4599                text[suggestion.old_text_range].to_string(),
4600                text[suggestion.new_text_range].to_string(),
4601            ));
4602        }
4603
4604        assert_eq!(
4605            suggestions,
4606            vec![
4607                (
4608                    Path::new("src/foo.rs").into(),
4609                    [
4610                        "    let a = 1;", //
4611                        "    let b = 2;",
4612                        "",
4613                    ]
4614                    .join("\n"),
4615                    [
4616                        "    let w = 1;",
4617                        "    let x = 2;",
4618                        "    let y = 3;",
4619                        "    let z = 4;",
4620                        "",
4621                    ]
4622                    .join("\n"),
4623                ),
4624                (
4625                    Path::new("src/foo.rs").into(),
4626                    [
4627                        "    let c = 1;", //
4628                        "",
4629                    ]
4630                    .join("\n"),
4631                    String::new(),
4632                )
4633            ]
4634        );
4635    }
4636
4637    #[gpui::test]
4638    async fn test_serialization(cx: &mut TestAppContext) {
4639        let settings_store = cx.update(SettingsStore::test);
4640        cx.set_global(settings_store);
4641        cx.set_global(CompletionProvider::Fake(FakeCompletionProvider::default()));
4642        cx.update(init);
4643        let registry = Arc::new(LanguageRegistry::test(cx.executor()));
4644        let conversation = cx.new_model(|cx| {
4645            Conversation::new(
4646                LanguageModel::default(),
4647                registry.clone(),
4648                Default::default(),
4649                None,
4650                None,
4651                cx,
4652            )
4653        });
4654        let buffer = conversation.read_with(cx, |conversation, _| conversation.buffer.clone());
4655        let message_0 =
4656            conversation.read_with(cx, |conversation, _| conversation.message_anchors[0].id);
4657        let message_1 = conversation.update(cx, |conversation, cx| {
4658            conversation
4659                .insert_message_after(message_0, Role::Assistant, MessageStatus::Done, cx)
4660                .unwrap()
4661        });
4662        let message_2 = conversation.update(cx, |conversation, cx| {
4663            conversation
4664                .insert_message_after(message_1.id, Role::System, MessageStatus::Done, cx)
4665                .unwrap()
4666        });
4667        buffer.update(cx, |buffer, cx| {
4668            buffer.edit([(0..0, "a"), (1..1, "b\nc")], None, cx);
4669            buffer.finalize_last_transaction();
4670        });
4671        let _message_3 = conversation.update(cx, |conversation, cx| {
4672            conversation
4673                .insert_message_after(message_2.id, Role::System, MessageStatus::Done, cx)
4674                .unwrap()
4675        });
4676        buffer.update(cx, |buffer, cx| buffer.undo(cx));
4677        assert_eq!(buffer.read_with(cx, |buffer, _| buffer.text()), "a\nb\nc\n");
4678        assert_eq!(
4679            cx.read(|cx| messages(&conversation, cx)),
4680            [
4681                (message_0, Role::User, 0..2),
4682                (message_1.id, Role::Assistant, 2..6),
4683                (message_2.id, Role::System, 6..6),
4684            ]
4685        );
4686
4687        let deserialized_conversation = Conversation::deserialize(
4688            conversation.read_with(cx, |conversation, cx| conversation.serialize(cx)),
4689            LanguageModel::default(),
4690            Default::default(),
4691            registry.clone(),
4692            Default::default(),
4693            None,
4694            None,
4695            &mut cx.to_async(),
4696        )
4697        .await
4698        .unwrap();
4699        let deserialized_buffer =
4700            deserialized_conversation.read_with(cx, |conversation, _| conversation.buffer.clone());
4701        assert_eq!(
4702            deserialized_buffer.read_with(cx, |buffer, _| buffer.text()),
4703            "a\nb\nc\n"
4704        );
4705        assert_eq!(
4706            cx.read(|cx| messages(&deserialized_conversation, cx)),
4707            [
4708                (message_0, Role::User, 0..2),
4709                (message_1.id, Role::Assistant, 2..6),
4710                (message_2.id, Role::System, 6..6),
4711            ]
4712        );
4713    }
4714
4715    fn messages(
4716        conversation: &Model<Conversation>,
4717        cx: &AppContext,
4718    ) -> Vec<(MessageId, Role, Range<usize>)> {
4719        conversation
4720            .read(cx)
4721            .messages(cx)
4722            .map(|message| (message.id, message.role, message.offset_range))
4723            .collect()
4724    }
4725}