assistant_panel.rs

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