assistant_panel.rs

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