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