assistant_panel.rs

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