assistant_panel.rs

   1use std::ops::Range;
   2use std::path::PathBuf;
   3use std::sync::Arc;
   4use std::time::Duration;
   5
   6use anyhow::{Result, anyhow};
   7use assistant_context_editor::{
   8    AssistantContext, AssistantPanelDelegate, ConfigurationError, ContextEditor, ContextEvent,
   9    SlashCommandCompletionProvider, humanize_token_count, make_lsp_adapter_delegate,
  10    render_remaining_tokens,
  11};
  12use assistant_settings::{AssistantDockPosition, AssistantSettings};
  13use assistant_slash_command::SlashCommandWorkingSet;
  14use assistant_tool::ToolWorkingSet;
  15
  16use client::{UserStore, zed_urls};
  17use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
  18use fs::Fs;
  19use gpui::{
  20    Action, Animation, AnimationExt as _, AnyElement, App, AsyncWindowContext, ClipboardItem,
  21    Corner, Entity, EventEmitter, FocusHandle, Focusable, FontWeight, KeyContext, Pixels,
  22    Subscription, Task, UpdateGlobal, WeakEntity, prelude::*, pulsating_between,
  23};
  24use language::LanguageRegistry;
  25use language_model::{LanguageModelProviderTosView, LanguageModelRegistry};
  26use language_model_selector::ToggleModelSelector;
  27use project::Project;
  28use prompt_store::{PromptBuilder, PromptStore, UserPromptId};
  29use proto::Plan;
  30use rules_library::{RulesLibrary, open_rules_library};
  31use settings::{Settings, update_settings_file};
  32use time::UtcOffset;
  33use ui::{
  34    Banner, ContextMenu, KeyBinding, PopoverMenu, PopoverMenuHandle, Tab, Tooltip, prelude::*,
  35};
  36use util::ResultExt as _;
  37use workspace::Workspace;
  38use workspace::dock::{DockPosition, Panel, PanelEvent};
  39use zed_actions::agent::OpenConfiguration;
  40use zed_actions::assistant::{OpenRulesLibrary, ToggleFocus};
  41
  42use crate::active_thread::{ActiveThread, ActiveThreadEvent};
  43use crate::assistant_configuration::{AssistantConfiguration, AssistantConfigurationEvent};
  44use crate::history_store::{HistoryEntry, HistoryStore};
  45use crate::message_editor::{MessageEditor, MessageEditorEvent};
  46use crate::thread::{Thread, ThreadError, ThreadId, TokenUsageRatio};
  47use crate::thread_history::{PastContext, PastThread, ThreadHistory};
  48use crate::thread_store::ThreadStore;
  49use crate::ui::UsageBanner;
  50use crate::{
  51    AddContextServer, AgentDiff, ExpandMessageEditor, InlineAssistant, NewTextThread, NewThread,
  52    OpenActiveThreadAsMarkdown, OpenAgentDiff, OpenHistory, ThreadEvent, ToggleContextPicker,
  53};
  54
  55pub fn init(cx: &mut App) {
  56    cx.observe_new(
  57        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
  58            workspace
  59                .register_action(|workspace, action: &NewThread, window, cx| {
  60                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  61                        panel.update(cx, |panel, cx| panel.new_thread(action, window, cx));
  62                        workspace.focus_panel::<AssistantPanel>(window, cx);
  63                    }
  64                })
  65                .register_action(|workspace, _: &OpenHistory, window, cx| {
  66                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  67                        workspace.focus_panel::<AssistantPanel>(window, cx);
  68                        panel.update(cx, |panel, cx| panel.open_history(window, cx));
  69                    }
  70                })
  71                .register_action(|workspace, _: &OpenConfiguration, window, cx| {
  72                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  73                        workspace.focus_panel::<AssistantPanel>(window, cx);
  74                        panel.update(cx, |panel, cx| panel.open_configuration(window, cx));
  75                    }
  76                })
  77                .register_action(|workspace, _: &NewTextThread, window, cx| {
  78                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  79                        workspace.focus_panel::<AssistantPanel>(window, cx);
  80                        panel.update(cx, |panel, cx| panel.new_prompt_editor(window, cx));
  81                    }
  82                })
  83                .register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
  84                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  85                        workspace.focus_panel::<AssistantPanel>(window, cx);
  86                        panel.update(cx, |panel, cx| {
  87                            panel.deploy_rules_library(action, window, cx)
  88                        });
  89                    }
  90                })
  91                .register_action(|workspace, _: &OpenAgentDiff, window, cx| {
  92                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  93                        workspace.focus_panel::<AssistantPanel>(window, cx);
  94                        let thread = panel.read(cx).thread.read(cx).thread().clone();
  95                        AgentDiff::deploy_in_workspace(thread, workspace, window, cx);
  96                    }
  97                })
  98                .register_action(|workspace, _: &ExpandMessageEditor, window, cx| {
  99                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 100                        workspace.focus_panel::<AssistantPanel>(window, cx);
 101                        panel.update(cx, |panel, cx| {
 102                            panel.message_editor.update(cx, |editor, cx| {
 103                                editor.expand_message_editor(&ExpandMessageEditor, window, cx);
 104                            });
 105                        });
 106                    }
 107                });
 108        },
 109    )
 110    .detach();
 111}
 112
 113enum ActiveView {
 114    Thread {
 115        change_title_editor: Entity<Editor>,
 116        _subscriptions: Vec<gpui::Subscription>,
 117    },
 118    PromptEditor {
 119        context_editor: Entity<ContextEditor>,
 120        title_editor: Entity<Editor>,
 121        _subscriptions: Vec<gpui::Subscription>,
 122    },
 123    History,
 124    Configuration,
 125}
 126
 127impl ActiveView {
 128    pub fn thread(thread: Entity<Thread>, window: &mut Window, cx: &mut App) -> Self {
 129        let summary = thread.read(cx).summary_or_default();
 130
 131        let editor = cx.new(|cx| {
 132            let mut editor = Editor::single_line(window, cx);
 133            editor.set_text(summary, window, cx);
 134            editor
 135        });
 136
 137        let subscriptions = vec![
 138            window.subscribe(&editor, cx, {
 139                {
 140                    let thread = thread.clone();
 141                    move |editor, event, window, cx| match event {
 142                        EditorEvent::BufferEdited => {
 143                            let new_summary = editor.read(cx).text(cx);
 144
 145                            thread.update(cx, |thread, cx| {
 146                                thread.set_summary(new_summary, cx);
 147                            })
 148                        }
 149                        EditorEvent::Blurred => {
 150                            if editor.read(cx).text(cx).is_empty() {
 151                                let summary = thread.read(cx).summary_or_default();
 152
 153                                editor.update(cx, |editor, cx| {
 154                                    editor.set_text(summary, window, cx);
 155                                });
 156                            }
 157                        }
 158                        _ => {}
 159                    }
 160                }
 161            }),
 162            window.subscribe(&thread, cx, {
 163                let editor = editor.clone();
 164                move |thread, event, window, cx| match event {
 165                    ThreadEvent::SummaryGenerated => {
 166                        let summary = thread.read(cx).summary_or_default();
 167
 168                        editor.update(cx, |editor, cx| {
 169                            editor.set_text(summary, window, cx);
 170                        })
 171                    }
 172                    _ => {}
 173                }
 174            }),
 175        ];
 176
 177        Self::Thread {
 178            change_title_editor: editor,
 179            _subscriptions: subscriptions,
 180        }
 181    }
 182
 183    pub fn prompt_editor(
 184        context_editor: Entity<ContextEditor>,
 185        window: &mut Window,
 186        cx: &mut App,
 187    ) -> Self {
 188        let title = context_editor.read(cx).title(cx).to_string();
 189
 190        let editor = cx.new(|cx| {
 191            let mut editor = Editor::single_line(window, cx);
 192            editor.set_text(title, window, cx);
 193            editor
 194        });
 195
 196        // This is a workaround for `editor.set_text` emitting a `BufferEdited` event, which would
 197        // cause a custom summary to be set. The presence of this custom summary would cause
 198        // summarization to not happen.
 199        let mut suppress_first_edit = true;
 200
 201        let subscriptions = vec![
 202            window.subscribe(&editor, cx, {
 203                {
 204                    let context_editor = context_editor.clone();
 205                    move |editor, event, window, cx| match event {
 206                        EditorEvent::BufferEdited => {
 207                            if suppress_first_edit {
 208                                suppress_first_edit = false;
 209                                return;
 210                            }
 211                            let new_summary = editor.read(cx).text(cx);
 212
 213                            context_editor.update(cx, |context_editor, cx| {
 214                                context_editor
 215                                    .context()
 216                                    .update(cx, |assistant_context, cx| {
 217                                        assistant_context.set_custom_summary(new_summary, cx);
 218                                    })
 219                            })
 220                        }
 221                        EditorEvent::Blurred => {
 222                            if editor.read(cx).text(cx).is_empty() {
 223                                let summary = context_editor
 224                                    .read(cx)
 225                                    .context()
 226                                    .read(cx)
 227                                    .summary_or_default();
 228
 229                                editor.update(cx, |editor, cx| {
 230                                    editor.set_text(summary, window, cx);
 231                                });
 232                            }
 233                        }
 234                        _ => {}
 235                    }
 236                }
 237            }),
 238            window.subscribe(&context_editor.read(cx).context().clone(), cx, {
 239                let editor = editor.clone();
 240                move |assistant_context, event, window, cx| match event {
 241                    ContextEvent::SummaryGenerated => {
 242                        let summary = assistant_context.read(cx).summary_or_default();
 243
 244                        editor.update(cx, |editor, cx| {
 245                            editor.set_text(summary, window, cx);
 246                        })
 247                    }
 248                    _ => {}
 249                }
 250            }),
 251        ];
 252
 253        Self::PromptEditor {
 254            context_editor,
 255            title_editor: editor,
 256            _subscriptions: subscriptions,
 257        }
 258    }
 259}
 260
 261pub struct AssistantPanel {
 262    workspace: WeakEntity<Workspace>,
 263    user_store: Entity<UserStore>,
 264    project: Entity<Project>,
 265    fs: Arc<dyn Fs>,
 266    language_registry: Arc<LanguageRegistry>,
 267    thread_store: Entity<ThreadStore>,
 268    thread: Entity<ActiveThread>,
 269    message_editor: Entity<MessageEditor>,
 270    _active_thread_subscriptions: Vec<Subscription>,
 271    _default_model_subscription: Subscription,
 272    context_store: Entity<assistant_context_editor::ContextStore>,
 273    prompt_store: Option<Entity<PromptStore>>,
 274    configuration: Option<Entity<AssistantConfiguration>>,
 275    configuration_subscription: Option<Subscription>,
 276    local_timezone: UtcOffset,
 277    active_view: ActiveView,
 278    previous_view: Option<ActiveView>,
 279    history_store: Entity<HistoryStore>,
 280    history: Entity<ThreadHistory>,
 281    assistant_dropdown_menu_handle: PopoverMenuHandle<ContextMenu>,
 282    width: Option<Pixels>,
 283    height: Option<Pixels>,
 284}
 285
 286impl AssistantPanel {
 287    pub fn load(
 288        workspace: WeakEntity<Workspace>,
 289        prompt_builder: Arc<PromptBuilder>,
 290        mut cx: AsyncWindowContext,
 291    ) -> Task<Result<Entity<Self>>> {
 292        let prompt_store = cx.update(|_window, cx| PromptStore::global(cx));
 293        cx.spawn(async move |cx| {
 294            let prompt_store = match prompt_store {
 295                Ok(prompt_store) => prompt_store.await.ok(),
 296                Err(_) => None,
 297            };
 298            let tools = cx.new(|_| ToolWorkingSet::default())?;
 299            let thread_store = workspace
 300                .update(cx, |workspace, cx| {
 301                    let project = workspace.project().clone();
 302                    ThreadStore::load(
 303                        project,
 304                        tools.clone(),
 305                        prompt_store.clone(),
 306                        prompt_builder.clone(),
 307                        cx,
 308                    )
 309                })?
 310                .await?;
 311
 312            let slash_commands = Arc::new(SlashCommandWorkingSet::default());
 313            let context_store = workspace
 314                .update(cx, |workspace, cx| {
 315                    let project = workspace.project().clone();
 316                    assistant_context_editor::ContextStore::new(
 317                        project,
 318                        prompt_builder.clone(),
 319                        slash_commands,
 320                        cx,
 321                    )
 322                })?
 323                .await?;
 324
 325            workspace.update_in(cx, |workspace, window, cx| {
 326                cx.new(|cx| {
 327                    Self::new(
 328                        workspace,
 329                        thread_store,
 330                        context_store,
 331                        prompt_store,
 332                        window,
 333                        cx,
 334                    )
 335                })
 336            })
 337        })
 338    }
 339
 340    fn new(
 341        workspace: &Workspace,
 342        thread_store: Entity<ThreadStore>,
 343        context_store: Entity<assistant_context_editor::ContextStore>,
 344        prompt_store: Option<Entity<PromptStore>>,
 345        window: &mut Window,
 346        cx: &mut Context<Self>,
 347    ) -> Self {
 348        let thread = thread_store.update(cx, |this, cx| this.create_thread(cx));
 349        let fs = workspace.app_state().fs.clone();
 350        let user_store = workspace.app_state().user_store.clone();
 351        let project = workspace.project();
 352        let language_registry = project.read(cx).languages().clone();
 353        let workspace = workspace.weak_handle();
 354        let weak_self = cx.entity().downgrade();
 355
 356        let message_editor_context_store = cx.new(|_cx| {
 357            crate::context_store::ContextStore::new(
 358                project.downgrade(),
 359                Some(thread_store.downgrade()),
 360            )
 361        });
 362
 363        let message_editor = cx.new(|cx| {
 364            MessageEditor::new(
 365                fs.clone(),
 366                workspace.clone(),
 367                message_editor_context_store.clone(),
 368                prompt_store.clone(),
 369                thread_store.downgrade(),
 370                thread.clone(),
 371                window,
 372                cx,
 373            )
 374        });
 375
 376        let message_editor_subscription =
 377            cx.subscribe(&message_editor, |_, _, event, cx| match event {
 378                MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
 379                    cx.notify();
 380                }
 381            });
 382
 383        let history_store =
 384            cx.new(|cx| HistoryStore::new(thread_store.clone(), context_store.clone(), cx));
 385
 386        cx.observe(&history_store, |_, _, cx| cx.notify()).detach();
 387
 388        let active_view = ActiveView::thread(thread.clone(), window, cx);
 389        let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
 390            if let ThreadEvent::MessageAdded(_) = &event {
 391                // needed to leave empty state
 392                cx.notify();
 393            }
 394        });
 395        let thread = cx.new(|cx| {
 396            ActiveThread::new(
 397                thread.clone(),
 398                thread_store.clone(),
 399                language_registry.clone(),
 400                workspace.clone(),
 401                window,
 402                cx,
 403            )
 404        });
 405
 406        let active_thread_subscription = cx.subscribe(&thread, |_, _, event, cx| match &event {
 407            ActiveThreadEvent::EditingMessageTokenCountChanged => {
 408                cx.notify();
 409            }
 410        });
 411
 412        let _default_model_subscription = cx.subscribe(
 413            &LanguageModelRegistry::global(cx),
 414            |this, _, event: &language_model::Event, cx| match event {
 415                language_model::Event::DefaultModelChanged => {
 416                    this.thread
 417                        .read(cx)
 418                        .thread()
 419                        .clone()
 420                        .update(cx, |thread, cx| thread.get_or_init_configured_model(cx));
 421                }
 422                _ => {}
 423            },
 424        );
 425
 426        Self {
 427            active_view,
 428            workspace,
 429            user_store,
 430            project: project.clone(),
 431            fs: fs.clone(),
 432            language_registry,
 433            thread_store: thread_store.clone(),
 434            thread,
 435            message_editor,
 436            _active_thread_subscriptions: vec![
 437                thread_subscription,
 438                active_thread_subscription,
 439                message_editor_subscription,
 440            ],
 441            _default_model_subscription,
 442            context_store,
 443            prompt_store,
 444            configuration: None,
 445            configuration_subscription: None,
 446            local_timezone: UtcOffset::from_whole_seconds(
 447                chrono::Local::now().offset().local_minus_utc(),
 448            )
 449            .unwrap(),
 450            previous_view: None,
 451            history_store: history_store.clone(),
 452            history: cx.new(|cx| ThreadHistory::new(weak_self, history_store, window, cx)),
 453            assistant_dropdown_menu_handle: PopoverMenuHandle::default(),
 454            width: None,
 455            height: None,
 456        }
 457    }
 458
 459    pub fn toggle_focus(
 460        workspace: &mut Workspace,
 461        _: &ToggleFocus,
 462        window: &mut Window,
 463        cx: &mut Context<Workspace>,
 464    ) {
 465        if workspace
 466            .panel::<Self>(cx)
 467            .is_some_and(|panel| panel.read(cx).enabled(cx))
 468        {
 469            workspace.toggle_panel_focus::<Self>(window, cx);
 470        }
 471    }
 472
 473    pub(crate) fn local_timezone(&self) -> UtcOffset {
 474        self.local_timezone
 475    }
 476
 477    pub(crate) fn prompt_store(&self) -> &Option<Entity<PromptStore>> {
 478        &self.prompt_store
 479    }
 480
 481    pub(crate) fn thread_store(&self) -> &Entity<ThreadStore> {
 482        &self.thread_store
 483    }
 484
 485    fn cancel(&mut self, _: &editor::actions::Cancel, window: &mut Window, cx: &mut Context<Self>) {
 486        self.thread
 487            .update(cx, |thread, cx| thread.cancel_last_completion(window, cx));
 488    }
 489
 490    fn new_thread(&mut self, action: &NewThread, window: &mut Window, cx: &mut Context<Self>) {
 491        let thread = self
 492            .thread_store
 493            .update(cx, |this, cx| this.create_thread(cx));
 494
 495        let thread_view = ActiveView::thread(thread.clone(), window, cx);
 496        self.set_active_view(thread_view, window, cx);
 497
 498        let message_editor_context_store = cx.new(|_cx| {
 499            crate::context_store::ContextStore::new(
 500                self.project.downgrade(),
 501                Some(self.thread_store.downgrade()),
 502            )
 503        });
 504
 505        if let Some(other_thread_id) = action.from_thread_id.clone() {
 506            let other_thread_task = self
 507                .thread_store
 508                .update(cx, |this, cx| this.open_thread(&other_thread_id, cx));
 509
 510            cx.spawn({
 511                let context_store = message_editor_context_store.clone();
 512
 513                async move |_panel, cx| {
 514                    let other_thread = other_thread_task.await?;
 515
 516                    context_store.update(cx, |this, cx| {
 517                        this.add_thread(other_thread, false, cx);
 518                    })?;
 519                    anyhow::Ok(())
 520                }
 521            })
 522            .detach_and_log_err(cx);
 523        }
 524
 525        let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
 526            if let ThreadEvent::MessageAdded(_) = &event {
 527                // needed to leave empty state
 528                cx.notify();
 529            }
 530        });
 531
 532        self.thread = cx.new(|cx| {
 533            ActiveThread::new(
 534                thread.clone(),
 535                self.thread_store.clone(),
 536                self.language_registry.clone(),
 537                self.workspace.clone(),
 538                window,
 539                cx,
 540            )
 541        });
 542
 543        let active_thread_subscription =
 544            cx.subscribe(&self.thread, |_, _, event, cx| match &event {
 545                ActiveThreadEvent::EditingMessageTokenCountChanged => {
 546                    cx.notify();
 547                }
 548            });
 549
 550        self.message_editor = cx.new(|cx| {
 551            MessageEditor::new(
 552                self.fs.clone(),
 553                self.workspace.clone(),
 554                message_editor_context_store,
 555                self.prompt_store.clone(),
 556                self.thread_store.downgrade(),
 557                thread,
 558                window,
 559                cx,
 560            )
 561        });
 562        self.message_editor.focus_handle(cx).focus(window);
 563
 564        let message_editor_subscription =
 565            cx.subscribe(&self.message_editor, |_, _, event, cx| match event {
 566                MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
 567                    cx.notify();
 568                }
 569            });
 570
 571        self._active_thread_subscriptions = vec![
 572            thread_subscription,
 573            active_thread_subscription,
 574            message_editor_subscription,
 575        ];
 576    }
 577
 578    fn new_prompt_editor(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 579        let context = self
 580            .context_store
 581            .update(cx, |context_store, cx| context_store.create(cx));
 582        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 583            .log_err()
 584            .flatten();
 585
 586        let context_editor = cx.new(|cx| {
 587            let mut editor = ContextEditor::for_context(
 588                context,
 589                self.fs.clone(),
 590                self.workspace.clone(),
 591                self.project.clone(),
 592                lsp_adapter_delegate,
 593                window,
 594                cx,
 595            );
 596            editor.insert_default_prompt(window, cx);
 597            editor
 598        });
 599
 600        self.set_active_view(
 601            ActiveView::prompt_editor(context_editor.clone(), window, cx),
 602            window,
 603            cx,
 604        );
 605        context_editor.focus_handle(cx).focus(window);
 606    }
 607
 608    fn deploy_rules_library(
 609        &mut self,
 610        action: &OpenRulesLibrary,
 611        _window: &mut Window,
 612        cx: &mut Context<Self>,
 613    ) {
 614        open_rules_library(
 615            self.language_registry.clone(),
 616            Box::new(PromptLibraryInlineAssist::new(self.workspace.clone())),
 617            Arc::new(|| {
 618                Box::new(SlashCommandCompletionProvider::new(
 619                    Arc::new(SlashCommandWorkingSet::default()),
 620                    None,
 621                    None,
 622                ))
 623            }),
 624            action
 625                .prompt_to_select
 626                .map(|uuid| UserPromptId(uuid).into()),
 627            cx,
 628        )
 629        .detach_and_log_err(cx);
 630    }
 631
 632    fn open_history(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 633        if matches!(self.active_view, ActiveView::History) {
 634            if let Some(previous_view) = self.previous_view.take() {
 635                self.set_active_view(previous_view, window, cx);
 636            }
 637        } else {
 638            self.thread_store
 639                .update(cx, |thread_store, cx| thread_store.reload(cx))
 640                .detach_and_log_err(cx);
 641            self.set_active_view(ActiveView::History, window, cx);
 642        }
 643        cx.notify();
 644    }
 645
 646    pub(crate) fn open_saved_prompt_editor(
 647        &mut self,
 648        path: PathBuf,
 649        window: &mut Window,
 650        cx: &mut Context<Self>,
 651    ) -> Task<Result<()>> {
 652        let context = self
 653            .context_store
 654            .update(cx, |store, cx| store.open_local_context(path.clone(), cx));
 655        let fs = self.fs.clone();
 656        let project = self.project.clone();
 657        let workspace = self.workspace.clone();
 658
 659        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
 660
 661        cx.spawn_in(window, async move |this, cx| {
 662            let context = context.await?;
 663            this.update_in(cx, |this, window, cx| {
 664                let editor = cx.new(|cx| {
 665                    ContextEditor::for_context(
 666                        context,
 667                        fs,
 668                        workspace,
 669                        project,
 670                        lsp_adapter_delegate,
 671                        window,
 672                        cx,
 673                    )
 674                });
 675
 676                this.set_active_view(
 677                    ActiveView::prompt_editor(editor.clone(), window, cx),
 678                    window,
 679                    cx,
 680                );
 681
 682                anyhow::Ok(())
 683            })??;
 684            Ok(())
 685        })
 686    }
 687
 688    pub(crate) fn open_thread(
 689        &mut self,
 690        thread_id: &ThreadId,
 691        window: &mut Window,
 692        cx: &mut Context<Self>,
 693    ) -> Task<Result<()>> {
 694        let open_thread_task = self
 695            .thread_store
 696            .update(cx, |this, cx| this.open_thread(thread_id, cx));
 697
 698        cx.spawn_in(window, async move |this, cx| {
 699            let thread = open_thread_task.await?;
 700            this.update_in(cx, |this, window, cx| {
 701                let thread_view = ActiveView::thread(thread.clone(), window, cx);
 702                this.set_active_view(thread_view, window, cx);
 703                let message_editor_context_store = cx.new(|_cx| {
 704                    crate::context_store::ContextStore::new(
 705                        this.project.downgrade(),
 706                        Some(this.thread_store.downgrade()),
 707                    )
 708                });
 709                let thread_subscription = cx.subscribe(&thread, |_, _, event, cx| {
 710                    if let ThreadEvent::MessageAdded(_) = &event {
 711                        // needed to leave empty state
 712                        cx.notify();
 713                    }
 714                });
 715
 716                this.thread = cx.new(|cx| {
 717                    ActiveThread::new(
 718                        thread.clone(),
 719                        this.thread_store.clone(),
 720                        this.language_registry.clone(),
 721                        this.workspace.clone(),
 722                        window,
 723                        cx,
 724                    )
 725                });
 726
 727                let active_thread_subscription =
 728                    cx.subscribe(&this.thread, |_, _, event, cx| match &event {
 729                        ActiveThreadEvent::EditingMessageTokenCountChanged => {
 730                            cx.notify();
 731                        }
 732                    });
 733
 734                this.message_editor = cx.new(|cx| {
 735                    MessageEditor::new(
 736                        this.fs.clone(),
 737                        this.workspace.clone(),
 738                        message_editor_context_store,
 739                        this.prompt_store.clone(),
 740                        this.thread_store.downgrade(),
 741                        thread,
 742                        window,
 743                        cx,
 744                    )
 745                });
 746                this.message_editor.focus_handle(cx).focus(window);
 747
 748                let message_editor_subscription =
 749                    cx.subscribe(&this.message_editor, |_, _, event, cx| match event {
 750                        MessageEditorEvent::Changed | MessageEditorEvent::EstimatedTokenCount => {
 751                            cx.notify();
 752                        }
 753                    });
 754
 755                this._active_thread_subscriptions = vec![
 756                    thread_subscription,
 757                    active_thread_subscription,
 758                    message_editor_subscription,
 759                ];
 760            })
 761        })
 762    }
 763
 764    pub fn go_back(&mut self, _: &workspace::GoBack, window: &mut Window, cx: &mut Context<Self>) {
 765        match self.active_view {
 766            ActiveView::Configuration | ActiveView::History => {
 767                self.active_view =
 768                    ActiveView::thread(self.thread.read(cx).thread().clone(), window, cx);
 769                self.message_editor.focus_handle(cx).focus(window);
 770                cx.notify();
 771            }
 772            _ => {}
 773        }
 774    }
 775
 776    pub fn open_agent_diff(
 777        &mut self,
 778        _: &OpenAgentDiff,
 779        window: &mut Window,
 780        cx: &mut Context<Self>,
 781    ) {
 782        let thread = self.thread.read(cx).thread().clone();
 783        self.workspace
 784            .update(cx, |workspace, cx| {
 785                AgentDiff::deploy_in_workspace(thread, workspace, window, cx)
 786            })
 787            .log_err();
 788    }
 789
 790    pub(crate) fn open_configuration(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 791        let context_server_manager = self.thread_store.read(cx).context_server_manager();
 792        let tools = self.thread_store.read(cx).tools();
 793        let fs = self.fs.clone();
 794
 795        self.set_active_view(ActiveView::Configuration, window, cx);
 796        self.configuration =
 797            Some(cx.new(|cx| {
 798                AssistantConfiguration::new(fs, context_server_manager, tools, window, cx)
 799            }));
 800
 801        if let Some(configuration) = self.configuration.as_ref() {
 802            self.configuration_subscription = Some(cx.subscribe_in(
 803                configuration,
 804                window,
 805                Self::handle_assistant_configuration_event,
 806            ));
 807
 808            configuration.focus_handle(cx).focus(window);
 809        }
 810    }
 811
 812    pub(crate) fn open_active_thread_as_markdown(
 813        &mut self,
 814        _: &OpenActiveThreadAsMarkdown,
 815        window: &mut Window,
 816        cx: &mut Context<Self>,
 817    ) {
 818        let Some(workspace) = self
 819            .workspace
 820            .upgrade()
 821            .ok_or_else(|| anyhow!("workspace dropped"))
 822            .log_err()
 823        else {
 824            return;
 825        };
 826
 827        let markdown_language_task = workspace
 828            .read(cx)
 829            .app_state()
 830            .languages
 831            .language_for_name("Markdown");
 832        let thread = self.active_thread(cx);
 833        cx.spawn_in(window, async move |_this, cx| {
 834            let markdown_language = markdown_language_task.await?;
 835
 836            workspace.update_in(cx, |workspace, window, cx| {
 837                let thread = thread.read(cx);
 838                let markdown = thread.to_markdown(cx)?;
 839                let thread_summary = thread
 840                    .summary()
 841                    .map(|summary| summary.to_string())
 842                    .unwrap_or_else(|| "Thread".to_string());
 843
 844                let project = workspace.project().clone();
 845                let buffer = project.update(cx, |project, cx| {
 846                    project.create_local_buffer(&markdown, Some(markdown_language), cx)
 847                });
 848                let buffer = cx.new(|cx| {
 849                    MultiBuffer::singleton(buffer, cx).with_title(thread_summary.clone())
 850                });
 851
 852                workspace.add_item_to_active_pane(
 853                    Box::new(cx.new(|cx| {
 854                        let mut editor =
 855                            Editor::for_multibuffer(buffer, Some(project.clone()), window, cx);
 856                        editor.set_breadcrumb_header(thread_summary);
 857                        editor
 858                    })),
 859                    None,
 860                    true,
 861                    window,
 862                    cx,
 863                );
 864
 865                anyhow::Ok(())
 866            })
 867        })
 868        .detach_and_log_err(cx);
 869    }
 870
 871    fn handle_assistant_configuration_event(
 872        &mut self,
 873        _entity: &Entity<AssistantConfiguration>,
 874        event: &AssistantConfigurationEvent,
 875        window: &mut Window,
 876        cx: &mut Context<Self>,
 877    ) {
 878        match event {
 879            AssistantConfigurationEvent::NewThread(provider) => {
 880                if LanguageModelRegistry::read_global(cx)
 881                    .default_model()
 882                    .map_or(true, |model| model.provider.id() != provider.id())
 883                {
 884                    if let Some(model) = provider.default_model(cx) {
 885                        update_settings_file::<AssistantSettings>(
 886                            self.fs.clone(),
 887                            cx,
 888                            move |settings, _| settings.set_model(model),
 889                        );
 890                    }
 891                }
 892
 893                self.new_thread(&NewThread::default(), window, cx);
 894            }
 895        }
 896    }
 897
 898    pub(crate) fn active_thread(&self, cx: &App) -> Entity<Thread> {
 899        self.thread.read(cx).thread().clone()
 900    }
 901
 902    pub(crate) fn delete_thread(
 903        &mut self,
 904        thread_id: &ThreadId,
 905        cx: &mut Context<Self>,
 906    ) -> Task<Result<()>> {
 907        self.thread_store
 908            .update(cx, |this, cx| this.delete_thread(thread_id, cx))
 909    }
 910
 911    pub(crate) fn has_active_thread(&self) -> bool {
 912        matches!(self.active_view, ActiveView::Thread { .. })
 913    }
 914
 915    pub(crate) fn active_context_editor(&self) -> Option<Entity<ContextEditor>> {
 916        match &self.active_view {
 917            ActiveView::PromptEditor { context_editor, .. } => Some(context_editor.clone()),
 918            _ => None,
 919        }
 920    }
 921
 922    pub(crate) fn delete_context(
 923        &mut self,
 924        path: PathBuf,
 925        cx: &mut Context<Self>,
 926    ) -> Task<Result<()>> {
 927        self.context_store
 928            .update(cx, |this, cx| this.delete_local_context(path, cx))
 929    }
 930
 931    fn set_active_view(
 932        &mut self,
 933        new_view: ActiveView,
 934        window: &mut Window,
 935        cx: &mut Context<Self>,
 936    ) {
 937        let current_is_history = matches!(self.active_view, ActiveView::History);
 938        let new_is_history = matches!(new_view, ActiveView::History);
 939
 940        if current_is_history && !new_is_history {
 941            self.active_view = new_view;
 942        } else if !current_is_history && new_is_history {
 943            self.previous_view = Some(std::mem::replace(&mut self.active_view, new_view));
 944        } else {
 945            if !new_is_history {
 946                self.previous_view = None;
 947            }
 948            self.active_view = new_view;
 949        }
 950
 951        self.focus_handle(cx).focus(window);
 952    }
 953}
 954
 955impl Focusable for AssistantPanel {
 956    fn focus_handle(&self, cx: &App) -> FocusHandle {
 957        match &self.active_view {
 958            ActiveView::Thread { .. } => self.message_editor.focus_handle(cx),
 959            ActiveView::History => self.history.focus_handle(cx),
 960            ActiveView::PromptEditor { context_editor, .. } => context_editor.focus_handle(cx),
 961            ActiveView::Configuration => {
 962                if let Some(configuration) = self.configuration.as_ref() {
 963                    configuration.focus_handle(cx)
 964                } else {
 965                    cx.focus_handle()
 966                }
 967            }
 968        }
 969    }
 970}
 971
 972impl EventEmitter<PanelEvent> for AssistantPanel {}
 973
 974impl Panel for AssistantPanel {
 975    fn persistent_name() -> &'static str {
 976        "AgentPanel"
 977    }
 978
 979    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
 980        match AssistantSettings::get_global(cx).dock {
 981            AssistantDockPosition::Left => DockPosition::Left,
 982            AssistantDockPosition::Bottom => DockPosition::Bottom,
 983            AssistantDockPosition::Right => DockPosition::Right,
 984        }
 985    }
 986
 987    fn position_is_valid(&self, _: DockPosition) -> bool {
 988        true
 989    }
 990
 991    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
 992        settings::update_settings_file::<AssistantSettings>(
 993            self.fs.clone(),
 994            cx,
 995            move |settings, _| {
 996                let dock = match position {
 997                    DockPosition::Left => AssistantDockPosition::Left,
 998                    DockPosition::Bottom => AssistantDockPosition::Bottom,
 999                    DockPosition::Right => AssistantDockPosition::Right,
1000                };
1001                settings.set_dock(dock);
1002            },
1003        );
1004    }
1005
1006    fn size(&self, window: &Window, cx: &App) -> Pixels {
1007        let settings = AssistantSettings::get_global(cx);
1008        match self.position(window, cx) {
1009            DockPosition::Left | DockPosition::Right => {
1010                self.width.unwrap_or(settings.default_width)
1011            }
1012            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1013        }
1014    }
1015
1016    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
1017        match self.position(window, cx) {
1018            DockPosition::Left | DockPosition::Right => self.width = size,
1019            DockPosition::Bottom => self.height = size,
1020        }
1021        cx.notify();
1022    }
1023
1024    fn set_active(&mut self, _active: bool, _window: &mut Window, _cx: &mut Context<Self>) {}
1025
1026    fn remote_id() -> Option<proto::PanelId> {
1027        Some(proto::PanelId::AssistantPanel)
1028    }
1029
1030    fn icon(&self, _window: &Window, cx: &App) -> Option<IconName> {
1031        (self.enabled(cx) && AssistantSettings::get_global(cx).button)
1032            .then_some(IconName::ZedAssistant)
1033    }
1034
1035    fn icon_tooltip(&self, _window: &Window, _cx: &App) -> Option<&'static str> {
1036        Some("Agent Panel")
1037    }
1038
1039    fn toggle_action(&self) -> Box<dyn Action> {
1040        Box::new(ToggleFocus)
1041    }
1042
1043    fn activation_priority(&self) -> u32 {
1044        3
1045    }
1046
1047    fn enabled(&self, cx: &App) -> bool {
1048        AssistantSettings::get_global(cx).enabled
1049    }
1050}
1051
1052impl AssistantPanel {
1053    fn render_title_view(&self, _window: &mut Window, cx: &Context<Self>) -> AnyElement {
1054        const LOADING_SUMMARY_PLACEHOLDER: &str = "Loading Summary…";
1055
1056        let content = match &self.active_view {
1057            ActiveView::Thread {
1058                change_title_editor,
1059                ..
1060            } => {
1061                let active_thread = self.thread.read(cx);
1062                let is_empty = active_thread.is_empty();
1063
1064                let summary = active_thread.summary(cx);
1065
1066                if is_empty {
1067                    Label::new(Thread::DEFAULT_SUMMARY.clone())
1068                        .truncate()
1069                        .ml_2()
1070                        .into_any_element()
1071                } else if summary.is_none() {
1072                    Label::new(LOADING_SUMMARY_PLACEHOLDER)
1073                        .ml_2()
1074                        .truncate()
1075                        .into_any_element()
1076                } else {
1077                    div()
1078                        .ml_2()
1079                        .w_full()
1080                        .child(change_title_editor.clone())
1081                        .into_any_element()
1082                }
1083            }
1084            ActiveView::PromptEditor {
1085                title_editor,
1086                context_editor,
1087                ..
1088            } => {
1089                let context_editor = context_editor.read(cx);
1090                let summary = context_editor.context().read(cx).summary();
1091
1092                match summary {
1093                    None => Label::new(AssistantContext::DEFAULT_SUMMARY.clone())
1094                        .truncate()
1095                        .ml_2()
1096                        .into_any_element(),
1097                    Some(summary) => {
1098                        if summary.done {
1099                            div()
1100                                .ml_2()
1101                                .w_full()
1102                                .child(title_editor.clone())
1103                                .into_any_element()
1104                        } else {
1105                            Label::new(LOADING_SUMMARY_PLACEHOLDER)
1106                                .ml_2()
1107                                .truncate()
1108                                .into_any_element()
1109                        }
1110                    }
1111                }
1112            }
1113            ActiveView::History => Label::new("History").truncate().into_any_element(),
1114            ActiveView::Configuration => Label::new("Settings").truncate().into_any_element(),
1115        };
1116
1117        h_flex()
1118            .key_context("TitleEditor")
1119            .id("TitleEditor")
1120            .flex_grow()
1121            .w_full()
1122            .max_w_full()
1123            .overflow_x_scroll()
1124            .child(content)
1125            .into_any()
1126    }
1127
1128    fn render_toolbar(&self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1129        let active_thread = self.thread.read(cx);
1130        let thread = active_thread.thread().read(cx);
1131        let thread_id = thread.id().clone();
1132        let is_empty = active_thread.is_empty();
1133        let is_history = matches!(self.active_view, ActiveView::History);
1134
1135        let show_token_count = match &self.active_view {
1136            ActiveView::Thread { .. } => !is_empty,
1137            ActiveView::PromptEditor { .. } => true,
1138            _ => false,
1139        };
1140
1141        let focus_handle = self.focus_handle(cx);
1142
1143        let go_back_button = match &self.active_view {
1144            ActiveView::History | ActiveView::Configuration => Some(
1145                div().pl_1().child(
1146                    IconButton::new("go-back", IconName::ArrowLeft)
1147                        .icon_size(IconSize::Small)
1148                        .on_click(cx.listener(|this, _, window, cx| {
1149                            this.go_back(&workspace::GoBack, window, cx);
1150                        }))
1151                        .tooltip({
1152                            let focus_handle = focus_handle.clone();
1153                            move |window, cx| {
1154                                Tooltip::for_action_in(
1155                                    "Go Back",
1156                                    &workspace::GoBack,
1157                                    &focus_handle,
1158                                    window,
1159                                    cx,
1160                                )
1161                            }
1162                        }),
1163                ),
1164            ),
1165            _ => None,
1166        };
1167
1168        h_flex()
1169            .id("assistant-toolbar")
1170            .h(Tab::container_height(cx))
1171            .max_w_full()
1172            .flex_none()
1173            .justify_between()
1174            .gap_2()
1175            .bg(cx.theme().colors().tab_bar_background)
1176            .border_b_1()
1177            .border_color(cx.theme().colors().border)
1178            .child(
1179                h_flex()
1180                    .w_full()
1181                    .gap_1()
1182                    .children(go_back_button)
1183                    .child(self.render_title_view(window, cx)),
1184            )
1185            .child(
1186                h_flex()
1187                    .h_full()
1188                    .gap_2()
1189                    .when(show_token_count, |parent|
1190                        parent.children(self.render_token_count(&thread, cx))
1191                    )
1192                    .child(
1193                        h_flex()
1194                            .h_full()
1195                            .gap(DynamicSpacing::Base02.rems(cx))
1196                            .px(DynamicSpacing::Base08.rems(cx))
1197                            .border_l_1()
1198                            .border_color(cx.theme().colors().border)
1199                            .child(
1200                                IconButton::new("new", IconName::Plus)
1201                                    .icon_size(IconSize::Small)
1202                                    .style(ButtonStyle::Subtle)
1203                                    .tooltip(move |window, cx| {
1204                                        Tooltip::for_action_in(
1205                                            "New Thread",
1206                                            &NewThread::default(),
1207                                            &focus_handle,
1208                                            window,
1209                                            cx,
1210                                        )
1211                                    })
1212                                    .on_click(move |_event, window, cx| {
1213                                        window.dispatch_action(
1214                                            NewThread::default().boxed_clone(),
1215                                            cx,
1216                                        );
1217                                    }),
1218                            )
1219                            .child(
1220                                IconButton::new("open-history", IconName::HistoryRerun)
1221                                    .icon_size(IconSize::Small)
1222                                    .toggle_state(is_history)
1223                                    .selected_icon_color(Color::Accent)
1224                                    .tooltip({
1225                                        let focus_handle = self.focus_handle(cx);
1226                                        move |window, cx| {
1227                                            Tooltip::for_action_in(
1228                                                "History",
1229                                                &OpenHistory,
1230                                                &focus_handle,
1231                                                window,
1232                                                cx,
1233                                            )
1234                                        }
1235                                    })
1236                                    .on_click(move |_event, window, cx| {
1237                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
1238                                    }),
1239                            )
1240                            .child(
1241                                PopoverMenu::new("assistant-menu")
1242                                    .trigger_with_tooltip(
1243                                        IconButton::new("new", IconName::Ellipsis)
1244                                            .icon_size(IconSize::Small)
1245                                            .style(ButtonStyle::Subtle),
1246                                        Tooltip::text("Toggle Agent Menu"),
1247                                    )
1248                                    .anchor(Corner::TopRight)
1249                                    .with_handle(self.assistant_dropdown_menu_handle.clone())
1250                                    .menu(move |window, cx| {
1251                                        Some(ContextMenu::build(
1252                                            window,
1253                                            cx,
1254                                            |menu, _window, _cx| {
1255                                                menu
1256                                                    .when(!is_empty, |menu| {
1257                                                        menu.action(
1258                                                            "Start New From Summary",
1259                                                            Box::new(NewThread {
1260                                                                from_thread_id: Some(thread_id.clone()),
1261                                                            }),
1262                                                        ).separator()
1263                                                    })
1264                                                    .action(
1265                                                    "New Text Thread",
1266                                                    NewTextThread.boxed_clone(),
1267                                                )
1268                                                .action("Rules Library", Box::new(OpenRulesLibrary::default()))
1269                                                .action("Settings", Box::new(OpenConfiguration))
1270                                                .separator()
1271                                                .header("MCPs")
1272                                                .action(
1273                                                    "View Server Extensions",
1274                                                    Box::new(zed_actions::Extensions {
1275                                                        category_filter: Some(
1276                                                            zed_actions::ExtensionCategoryFilter::ContextServers,
1277                                                        ),
1278                                                        }),
1279                                                )
1280                                                .action("Add Custom Server", Box::new(AddContextServer))
1281                                            },
1282                                        ))
1283                                    }),
1284                            ),
1285                    ),
1286            )
1287    }
1288
1289    fn render_token_count(&self, thread: &Thread, cx: &App) -> Option<AnyElement> {
1290        let is_generating = thread.is_generating();
1291        let message_editor = self.message_editor.read(cx);
1292
1293        let conversation_token_usage = thread.total_token_usage()?;
1294
1295        let (total_token_usage, is_estimating) = if let Some((editing_message_id, unsent_tokens)) =
1296            self.thread.read(cx).editing_message_id()
1297        {
1298            let combined = thread
1299                .token_usage_up_to_message(editing_message_id)
1300                .add(unsent_tokens);
1301
1302            (combined, unsent_tokens > 0)
1303        } else {
1304            let unsent_tokens = message_editor.last_estimated_token_count().unwrap_or(0);
1305            let combined = conversation_token_usage.add(unsent_tokens);
1306
1307            (combined, unsent_tokens > 0)
1308        };
1309
1310        let is_waiting_to_update_token_count = message_editor.is_waiting_to_update_token_count();
1311
1312        match &self.active_view {
1313            ActiveView::Thread { .. } => {
1314                if total_token_usage.total == 0 {
1315                    return None;
1316                }
1317
1318                let token_color = match total_token_usage.ratio() {
1319                    TokenUsageRatio::Normal if is_estimating => Color::Default,
1320                    TokenUsageRatio::Normal => Color::Muted,
1321                    TokenUsageRatio::Warning => Color::Warning,
1322                    TokenUsageRatio::Exceeded => Color::Error,
1323                };
1324
1325                let token_count = h_flex()
1326                    .id("token-count")
1327                    .flex_shrink_0()
1328                    .gap_0p5()
1329                    .when(!is_generating && is_estimating, |parent| {
1330                        parent
1331                            .child(
1332                                h_flex()
1333                                    .mr_1()
1334                                    .size_2p5()
1335                                    .justify_center()
1336                                    .rounded_full()
1337                                    .bg(cx.theme().colors().text.opacity(0.1))
1338                                    .child(
1339                                        div().size_1().rounded_full().bg(cx.theme().colors().text),
1340                                    ),
1341                            )
1342                            .tooltip(move |window, cx| {
1343                                Tooltip::with_meta(
1344                                    "Estimated New Token Count",
1345                                    None,
1346                                    format!(
1347                                        "Current Conversation Tokens: {}",
1348                                        humanize_token_count(conversation_token_usage.total)
1349                                    ),
1350                                    window,
1351                                    cx,
1352                                )
1353                            })
1354                    })
1355                    .child(
1356                        Label::new(humanize_token_count(total_token_usage.total))
1357                            .size(LabelSize::Small)
1358                            .color(token_color)
1359                            .map(|label| {
1360                                if is_generating || is_waiting_to_update_token_count {
1361                                    label
1362                                        .with_animation(
1363                                            "used-tokens-label",
1364                                            Animation::new(Duration::from_secs(2))
1365                                                .repeat()
1366                                                .with_easing(pulsating_between(0.6, 1.)),
1367                                            |label, delta| label.alpha(delta),
1368                                        )
1369                                        .into_any()
1370                                } else {
1371                                    label.into_any_element()
1372                                }
1373                            }),
1374                    )
1375                    .child(Label::new("/").size(LabelSize::Small).color(Color::Muted))
1376                    .child(
1377                        Label::new(humanize_token_count(total_token_usage.max))
1378                            .size(LabelSize::Small)
1379                            .color(Color::Muted),
1380                    )
1381                    .into_any();
1382
1383                Some(token_count)
1384            }
1385            ActiveView::PromptEditor { context_editor, .. } => {
1386                let element = render_remaining_tokens(context_editor, cx)?;
1387
1388                Some(element.into_any_element())
1389            }
1390            _ => None,
1391        }
1392    }
1393
1394    fn render_active_thread_or_empty_state(
1395        &self,
1396        window: &mut Window,
1397        cx: &mut Context<Self>,
1398    ) -> AnyElement {
1399        if self.thread.read(cx).is_empty() {
1400            return self
1401                .render_thread_empty_state(window, cx)
1402                .into_any_element();
1403        }
1404
1405        self.thread.clone().into_any_element()
1406    }
1407
1408    fn configuration_error(&self, cx: &App) -> Option<ConfigurationError> {
1409        let Some(model) = LanguageModelRegistry::read_global(cx).default_model() else {
1410            return Some(ConfigurationError::NoProvider);
1411        };
1412
1413        if !model.provider.is_authenticated(cx) {
1414            return Some(ConfigurationError::ProviderNotAuthenticated);
1415        }
1416
1417        if model.provider.must_accept_terms(cx) {
1418            return Some(ConfigurationError::ProviderPendingTermsAcceptance(
1419                model.provider,
1420            ));
1421        }
1422
1423        None
1424    }
1425
1426    fn render_thread_empty_state(
1427        &self,
1428        window: &mut Window,
1429        cx: &mut Context<Self>,
1430    ) -> impl IntoElement {
1431        let recent_history = self
1432            .history_store
1433            .update(cx, |this, cx| this.recent_entries(6, cx));
1434
1435        let configuration_error = self.configuration_error(cx);
1436        let no_error = configuration_error.is_none();
1437        let focus_handle = self.focus_handle(cx);
1438
1439        v_flex()
1440            .size_full()
1441            .when(recent_history.is_empty(), |this| {
1442                let configuration_error_ref = &configuration_error;
1443                this.child(
1444                    v_flex()
1445                        .size_full()
1446                        .max_w_80()
1447                        .mx_auto()
1448                        .justify_center()
1449                        .items_center()
1450                        .gap_1()
1451                        .child(
1452                            h_flex().child(
1453                                Headline::new("Welcome to the Agent Panel")
1454                            ),
1455                        )
1456                        .when(no_error, |parent| {
1457                            parent
1458                                .child(
1459                                    h_flex().child(
1460                                        Label::new("Ask and build anything.")
1461                                            .color(Color::Muted)
1462                                            .mb_2p5(),
1463                                    ),
1464                                )
1465                                .child(
1466                                    Button::new("new-thread", "Start New Thread")
1467                                        .icon(IconName::Plus)
1468                                        .icon_position(IconPosition::Start)
1469                                        .icon_size(IconSize::Small)
1470                                        .icon_color(Color::Muted)
1471                                        .full_width()
1472                                        .key_binding(KeyBinding::for_action_in(
1473                                            &NewThread::default(),
1474                                            &focus_handle,
1475                                            window,
1476                                            cx,
1477                                        ))
1478                                        .on_click(|_event, window, cx| {
1479                                            window.dispatch_action(NewThread::default().boxed_clone(), cx)
1480                                        }),
1481                                )
1482                                .child(
1483                                    Button::new("context", "Add Context")
1484                                        .icon(IconName::FileCode)
1485                                        .icon_position(IconPosition::Start)
1486                                        .icon_size(IconSize::Small)
1487                                        .icon_color(Color::Muted)
1488                                        .full_width()
1489                                        .key_binding(KeyBinding::for_action_in(
1490                                            &ToggleContextPicker,
1491                                            &focus_handle,
1492                                            window,
1493                                            cx,
1494                                        ))
1495                                        .on_click(|_event, window, cx| {
1496                                            window.dispatch_action(ToggleContextPicker.boxed_clone(), cx)
1497                                        }),
1498                                )
1499                                .child(
1500                                    Button::new("mode", "Switch Model")
1501                                        .icon(IconName::DatabaseZap)
1502                                        .icon_position(IconPosition::Start)
1503                                        .icon_size(IconSize::Small)
1504                                        .icon_color(Color::Muted)
1505                                        .full_width()
1506                                        .key_binding(KeyBinding::for_action_in(
1507                                            &ToggleModelSelector,
1508                                            &focus_handle,
1509                                            window,
1510                                            cx,
1511                                        ))
1512                                        .on_click(|_event, window, cx| {
1513                                            window.dispatch_action(ToggleModelSelector.boxed_clone(), cx)
1514                                        }),
1515                                )
1516                                .child(
1517                                    Button::new("settings", "View Settings")
1518                                        .icon(IconName::Settings)
1519                                        .icon_position(IconPosition::Start)
1520                                        .icon_size(IconSize::Small)
1521                                        .icon_color(Color::Muted)
1522                                        .full_width()
1523                                        .key_binding(KeyBinding::for_action_in(
1524                                            &OpenConfiguration,
1525                                            &focus_handle,
1526                                            window,
1527                                            cx,
1528                                        ))
1529                                        .on_click(|_event, window, cx| {
1530                                            window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
1531                                        }),
1532                                )
1533                        })
1534                        .map(|parent| {
1535                            match configuration_error_ref {
1536                                Some(ConfigurationError::ProviderNotAuthenticated)
1537                                | Some(ConfigurationError::NoProvider) => {
1538                                    parent
1539                                        .child(
1540                                            h_flex().child(
1541                                                Label::new("To start using the agent, configure at least one LLM provider.")
1542                                                    .color(Color::Muted)
1543                                                    .mb_2p5()
1544                                            )
1545                                        )
1546                                        .child(
1547                                            Button::new("settings", "Configure a Provider")
1548                                                .icon(IconName::Settings)
1549                                                .icon_position(IconPosition::Start)
1550                                                .icon_size(IconSize::Small)
1551                                                .icon_color(Color::Muted)
1552                                                .full_width()
1553                                                .key_binding(KeyBinding::for_action_in(
1554                                                    &OpenConfiguration,
1555                                                    &focus_handle,
1556                                                    window,
1557                                                    cx,
1558                                                ))
1559                                                .on_click(|_event, window, cx| {
1560                                                    window.dispatch_action(OpenConfiguration.boxed_clone(), cx)
1561                                                }),
1562                                        )
1563                                }
1564                                Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
1565                                    parent.children(
1566                                        provider.render_accept_terms(
1567                                            LanguageModelProviderTosView::ThreadFreshStart,
1568                                            cx,
1569                                        ),
1570                                    )
1571                                }
1572                                None => parent,
1573                            }
1574                        })
1575                )
1576            })
1577            .when(!recent_history.is_empty(), |parent| {
1578                let focus_handle = focus_handle.clone();
1579                let configuration_error_ref = &configuration_error;
1580
1581                parent
1582                    .overflow_hidden()
1583                    .p_1p5()
1584                    .justify_end()
1585                    .gap_1()
1586                    .child(
1587                        h_flex()
1588                            .pl_1p5()
1589                            .pb_1()
1590                            .w_full()
1591                            .justify_between()
1592                            .border_b_1()
1593                            .border_color(cx.theme().colors().border_variant)
1594                            .child(
1595                                Label::new("Past Interactions")
1596                                    .size(LabelSize::Small)
1597                                    .color(Color::Muted),
1598                            )
1599                            .child(
1600                                Button::new("view-history", "View All")
1601                                    .style(ButtonStyle::Subtle)
1602                                    .label_size(LabelSize::Small)
1603                                    .key_binding(
1604                                        KeyBinding::for_action_in(
1605                                            &OpenHistory,
1606                                            &self.focus_handle(cx),
1607                                            window,
1608                                            cx,
1609                                        ).map(|kb| kb.size(rems_from_px(12.))),
1610                                    )
1611                                    .on_click(move |_event, window, cx| {
1612                                        window.dispatch_action(OpenHistory.boxed_clone(), cx);
1613                                    }),
1614                            ),
1615                    )
1616                    .child(
1617                        v_flex()
1618                            .gap_1()
1619                            .children(
1620                                recent_history.into_iter().map(|entry| {
1621                                    // TODO: Add keyboard navigation.
1622                                    match entry {
1623                                        HistoryEntry::Thread(thread) => {
1624                                            PastThread::new(thread, cx.entity().downgrade(), false, vec![])
1625                                                .into_any_element()
1626                                        }
1627                                        HistoryEntry::Context(context) => {
1628                                            PastContext::new(context, cx.entity().downgrade(), false, vec![])
1629                                                .into_any_element()
1630                                        }
1631                                    }
1632                                }),
1633                            )
1634                    )
1635                    .map(|parent| {
1636                        match configuration_error_ref {
1637                            Some(ConfigurationError::ProviderNotAuthenticated)
1638                            | Some(ConfigurationError::NoProvider) => {
1639                                parent
1640                                    .child(
1641                                        Banner::new()
1642                                            .severity(ui::Severity::Warning)
1643                                            .children(
1644                                                Label::new(
1645                                                    "Configure at least one LLM provider to start using the panel.",
1646                                                )
1647                                                .size(LabelSize::Small),
1648                                            )
1649                                            .action_slot(
1650                                                Button::new("settings", "Configure Provider")
1651                                                    .style(ButtonStyle::Tinted(ui::TintColor::Warning))
1652                                                    .label_size(LabelSize::Small)
1653                                                    .key_binding(
1654                                                        KeyBinding::for_action_in(
1655                                                            &OpenConfiguration,
1656                                                            &focus_handle,
1657                                                            window,
1658                                                            cx,
1659                                                        )
1660                                                        .map(|kb| kb.size(rems_from_px(12.))),
1661                                                    )
1662                                                    .on_click(|_event, window, cx| {
1663                                                        window.dispatch_action(
1664                                                            OpenConfiguration.boxed_clone(),
1665                                                            cx,
1666                                                        )
1667                                                    }),
1668                                            ),
1669                                    )
1670                            }
1671                            Some(ConfigurationError::ProviderPendingTermsAcceptance(provider)) => {
1672                                parent
1673                                    .child(
1674                                        Banner::new()
1675                                            .severity(ui::Severity::Warning)
1676                                            .children(
1677                                                h_flex()
1678                                                    .w_full()
1679                                                    .children(
1680                                                        provider.render_accept_terms(
1681                                                            LanguageModelProviderTosView::ThreadtEmptyState,
1682                                                            cx,
1683                                                        ),
1684                                                    ),
1685                                            ),
1686                                    )
1687                            }
1688                            None => parent,
1689                        }
1690                    })
1691            })
1692    }
1693
1694    fn render_usage_banner(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
1695        let plan = self
1696            .user_store
1697            .read(cx)
1698            .current_plan()
1699            .map(|plan| match plan {
1700                Plan::Free => zed_llm_client::Plan::Free,
1701                Plan::ZedPro => zed_llm_client::Plan::ZedPro,
1702                Plan::ZedProTrial => zed_llm_client::Plan::ZedProTrial,
1703            })
1704            .unwrap_or(zed_llm_client::Plan::Free);
1705        let usage = self.thread.read(cx).last_usage()?;
1706
1707        Some(UsageBanner::new(plan, usage).into_any_element())
1708    }
1709
1710    fn render_last_error(&self, cx: &mut Context<Self>) -> Option<AnyElement> {
1711        let last_error = self.thread.read(cx).last_error()?;
1712
1713        Some(
1714            div()
1715                .absolute()
1716                .right_3()
1717                .bottom_12()
1718                .max_w_96()
1719                .py_2()
1720                .px_3()
1721                .elevation_2(cx)
1722                .occlude()
1723                .child(match last_error {
1724                    ThreadError::PaymentRequired => self.render_payment_required_error(cx),
1725                    ThreadError::MaxMonthlySpendReached => {
1726                        self.render_max_monthly_spend_reached_error(cx)
1727                    }
1728                    ThreadError::ModelRequestLimitReached { plan } => {
1729                        self.render_model_request_limit_reached_error(plan, cx)
1730                    }
1731                    ThreadError::Message { header, message } => {
1732                        self.render_error_message(header, message, cx)
1733                    }
1734                })
1735                .into_any(),
1736        )
1737    }
1738
1739    fn render_payment_required_error(&self, cx: &mut Context<Self>) -> AnyElement {
1740        const ERROR_MESSAGE: &str = "Free tier exceeded. Subscribe and add payment to continue using Zed LLMs. You'll be billed at cost for tokens used.";
1741
1742        v_flex()
1743            .gap_0p5()
1744            .child(
1745                h_flex()
1746                    .gap_1p5()
1747                    .items_center()
1748                    .child(Icon::new(IconName::XCircle).color(Color::Error))
1749                    .child(Label::new("Free Usage Exceeded").weight(FontWeight::MEDIUM)),
1750            )
1751            .child(
1752                div()
1753                    .id("error-message")
1754                    .max_h_24()
1755                    .overflow_y_scroll()
1756                    .child(Label::new(ERROR_MESSAGE)),
1757            )
1758            .child(
1759                h_flex()
1760                    .justify_end()
1761                    .mt_1()
1762                    .gap_1()
1763                    .child(self.create_copy_button(ERROR_MESSAGE))
1764                    .child(Button::new("subscribe", "Subscribe").on_click(cx.listener(
1765                        |this, _, _, cx| {
1766                            this.thread.update(cx, |this, _cx| {
1767                                this.clear_last_error();
1768                            });
1769
1770                            cx.open_url(&zed_urls::account_url(cx));
1771                            cx.notify();
1772                        },
1773                    )))
1774                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
1775                        |this, _, _, cx| {
1776                            this.thread.update(cx, |this, _cx| {
1777                                this.clear_last_error();
1778                            });
1779
1780                            cx.notify();
1781                        },
1782                    ))),
1783            )
1784            .into_any()
1785    }
1786
1787    fn render_max_monthly_spend_reached_error(&self, cx: &mut Context<Self>) -> AnyElement {
1788        const ERROR_MESSAGE: &str = "You have reached your maximum monthly spend. Increase your spend limit to continue using Zed LLMs.";
1789
1790        v_flex()
1791            .gap_0p5()
1792            .child(
1793                h_flex()
1794                    .gap_1p5()
1795                    .items_center()
1796                    .child(Icon::new(IconName::XCircle).color(Color::Error))
1797                    .child(Label::new("Max Monthly Spend Reached").weight(FontWeight::MEDIUM)),
1798            )
1799            .child(
1800                div()
1801                    .id("error-message")
1802                    .max_h_24()
1803                    .overflow_y_scroll()
1804                    .child(Label::new(ERROR_MESSAGE)),
1805            )
1806            .child(
1807                h_flex()
1808                    .justify_end()
1809                    .mt_1()
1810                    .gap_1()
1811                    .child(self.create_copy_button(ERROR_MESSAGE))
1812                    .child(
1813                        Button::new("subscribe", "Update Monthly Spend Limit").on_click(
1814                            cx.listener(|this, _, _, cx| {
1815                                this.thread.update(cx, |this, _cx| {
1816                                    this.clear_last_error();
1817                                });
1818
1819                                cx.open_url(&zed_urls::account_url(cx));
1820                                cx.notify();
1821                            }),
1822                        ),
1823                    )
1824                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
1825                        |this, _, _, cx| {
1826                            this.thread.update(cx, |this, _cx| {
1827                                this.clear_last_error();
1828                            });
1829
1830                            cx.notify();
1831                        },
1832                    ))),
1833            )
1834            .into_any()
1835    }
1836
1837    fn render_model_request_limit_reached_error(
1838        &self,
1839        plan: Plan,
1840        cx: &mut Context<Self>,
1841    ) -> AnyElement {
1842        let error_message = match plan {
1843            Plan::ZedPro => {
1844                "Model request limit reached. Upgrade to usage-based billing for more requests."
1845            }
1846            Plan::ZedProTrial => {
1847                "Model request limit reached. Upgrade to Zed Pro for more requests."
1848            }
1849            Plan::Free => "Model request limit reached. Upgrade to Zed Pro for more requests.",
1850        };
1851        let call_to_action = match plan {
1852            Plan::ZedPro => "Upgrade to usage-based billing",
1853            Plan::ZedProTrial => "Upgrade to Zed Pro",
1854            Plan::Free => "Upgrade to Zed Pro",
1855        };
1856
1857        v_flex()
1858            .gap_0p5()
1859            .child(
1860                h_flex()
1861                    .gap_1p5()
1862                    .items_center()
1863                    .child(Icon::new(IconName::XCircle).color(Color::Error))
1864                    .child(Label::new("Model Request Limit Reached").weight(FontWeight::MEDIUM)),
1865            )
1866            .child(
1867                div()
1868                    .id("error-message")
1869                    .max_h_24()
1870                    .overflow_y_scroll()
1871                    .child(Label::new(error_message)),
1872            )
1873            .child(
1874                h_flex()
1875                    .justify_end()
1876                    .mt_1()
1877                    .gap_1()
1878                    .child(self.create_copy_button(error_message))
1879                    .child(
1880                        Button::new("subscribe", call_to_action).on_click(cx.listener(
1881                            |this, _, _, cx| {
1882                                this.thread.update(cx, |this, _cx| {
1883                                    this.clear_last_error();
1884                                });
1885
1886                                cx.open_url(&zed_urls::account_url(cx));
1887                                cx.notify();
1888                            },
1889                        )),
1890                    )
1891                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
1892                        |this, _, _, cx| {
1893                            this.thread.update(cx, |this, _cx| {
1894                                this.clear_last_error();
1895                            });
1896
1897                            cx.notify();
1898                        },
1899                    ))),
1900            )
1901            .into_any()
1902    }
1903
1904    fn render_error_message(
1905        &self,
1906        header: SharedString,
1907        message: SharedString,
1908        cx: &mut Context<Self>,
1909    ) -> AnyElement {
1910        let message_with_header = format!("{}\n{}", header, message);
1911        v_flex()
1912            .gap_0p5()
1913            .child(
1914                h_flex()
1915                    .gap_1p5()
1916                    .items_center()
1917                    .child(Icon::new(IconName::XCircle).color(Color::Error))
1918                    .child(Label::new(header).weight(FontWeight::MEDIUM)),
1919            )
1920            .child(
1921                div()
1922                    .id("error-message")
1923                    .max_h_32()
1924                    .overflow_y_scroll()
1925                    .child(Label::new(message.clone())),
1926            )
1927            .child(
1928                h_flex()
1929                    .justify_end()
1930                    .mt_1()
1931                    .gap_1()
1932                    .child(self.create_copy_button(message_with_header))
1933                    .child(Button::new("dismiss", "Dismiss").on_click(cx.listener(
1934                        |this, _, _, cx| {
1935                            this.thread.update(cx, |this, _cx| {
1936                                this.clear_last_error();
1937                            });
1938
1939                            cx.notify();
1940                        },
1941                    ))),
1942            )
1943            .into_any()
1944    }
1945
1946    fn create_copy_button(&self, message: impl Into<String>) -> impl IntoElement {
1947        let message = message.into();
1948        IconButton::new("copy", IconName::Copy)
1949            .on_click(move |_, _, cx| {
1950                cx.write_to_clipboard(ClipboardItem::new_string(message.clone()))
1951            })
1952            .tooltip(Tooltip::text("Copy Error Message"))
1953    }
1954
1955    fn key_context(&self) -> KeyContext {
1956        let mut key_context = KeyContext::new_with_defaults();
1957        key_context.add("AgentPanel");
1958        if matches!(self.active_view, ActiveView::PromptEditor { .. }) {
1959            key_context.add("prompt_editor");
1960        }
1961        key_context
1962    }
1963}
1964
1965impl Render for AssistantPanel {
1966    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1967        v_flex()
1968            .key_context(self.key_context())
1969            .justify_between()
1970            .size_full()
1971            .on_action(cx.listener(Self::cancel))
1972            .on_action(cx.listener(|this, action: &NewThread, window, cx| {
1973                this.new_thread(action, window, cx);
1974            }))
1975            .on_action(cx.listener(|this, _: &OpenHistory, window, cx| {
1976                this.open_history(window, cx);
1977            }))
1978            .on_action(cx.listener(|this, _: &OpenConfiguration, window, cx| {
1979                this.open_configuration(window, cx);
1980            }))
1981            .on_action(cx.listener(Self::open_active_thread_as_markdown))
1982            .on_action(cx.listener(Self::deploy_rules_library))
1983            .on_action(cx.listener(Self::open_agent_diff))
1984            .on_action(cx.listener(Self::go_back))
1985            .child(self.render_toolbar(window, cx))
1986            .map(|parent| match &self.active_view {
1987                ActiveView::Thread { .. } => parent
1988                    .child(self.render_active_thread_or_empty_state(window, cx))
1989                    .children(self.render_usage_banner(cx))
1990                    .child(h_flex().child(self.message_editor.clone()))
1991                    .children(self.render_last_error(cx)),
1992                ActiveView::History => parent.child(self.history.clone()),
1993                ActiveView::PromptEditor { context_editor, .. } => {
1994                    parent.child(context_editor.clone())
1995                }
1996                ActiveView::Configuration => parent.children(self.configuration.clone()),
1997            })
1998    }
1999}
2000
2001struct PromptLibraryInlineAssist {
2002    workspace: WeakEntity<Workspace>,
2003}
2004
2005impl PromptLibraryInlineAssist {
2006    pub fn new(workspace: WeakEntity<Workspace>) -> Self {
2007        Self { workspace }
2008    }
2009}
2010
2011impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
2012    fn assist(
2013        &self,
2014        prompt_editor: &Entity<Editor>,
2015        _initial_prompt: Option<String>,
2016        window: &mut Window,
2017        cx: &mut Context<RulesLibrary>,
2018    ) {
2019        InlineAssistant::update_global(cx, |assistant, cx| {
2020            let Some(project) = self
2021                .workspace
2022                .upgrade()
2023                .map(|workspace| workspace.read(cx).project().downgrade())
2024            else {
2025                return;
2026            };
2027            let prompt_store = None;
2028            let thread_store = None;
2029            assistant.assist(
2030                &prompt_editor,
2031                self.workspace.clone(),
2032                project,
2033                prompt_store,
2034                thread_store,
2035                window,
2036                cx,
2037            )
2038        })
2039    }
2040
2041    fn focus_assistant_panel(
2042        &self,
2043        workspace: &mut Workspace,
2044        window: &mut Window,
2045        cx: &mut Context<Workspace>,
2046    ) -> bool {
2047        workspace
2048            .focus_panel::<AssistantPanel>(window, cx)
2049            .is_some()
2050    }
2051}
2052
2053pub struct ConcreteAssistantPanelDelegate;
2054
2055impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
2056    fn active_context_editor(
2057        &self,
2058        workspace: &mut Workspace,
2059        _window: &mut Window,
2060        cx: &mut Context<Workspace>,
2061    ) -> Option<Entity<ContextEditor>> {
2062        let panel = workspace.panel::<AssistantPanel>(cx)?;
2063        panel.read(cx).active_context_editor()
2064    }
2065
2066    fn open_saved_context(
2067        &self,
2068        workspace: &mut Workspace,
2069        path: std::path::PathBuf,
2070        window: &mut Window,
2071        cx: &mut Context<Workspace>,
2072    ) -> Task<Result<()>> {
2073        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2074            return Task::ready(Err(anyhow!("Agent panel not found")));
2075        };
2076
2077        panel.update(cx, |panel, cx| {
2078            panel.open_saved_prompt_editor(path, window, cx)
2079        })
2080    }
2081
2082    fn open_remote_context(
2083        &self,
2084        _workspace: &mut Workspace,
2085        _context_id: assistant_context_editor::ContextId,
2086        _window: &mut Window,
2087        _cx: &mut Context<Workspace>,
2088    ) -> Task<Result<Entity<ContextEditor>>> {
2089        Task::ready(Err(anyhow!("opening remote context not implemented")))
2090    }
2091
2092    fn quote_selection(
2093        &self,
2094        workspace: &mut Workspace,
2095        selection_ranges: Vec<Range<Anchor>>,
2096        buffer: Entity<MultiBuffer>,
2097        window: &mut Window,
2098        cx: &mut Context<Workspace>,
2099    ) {
2100        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
2101            return;
2102        };
2103
2104        if !panel.focus_handle(cx).contains_focused(window, cx) {
2105            workspace.toggle_panel_focus::<AssistantPanel>(window, cx);
2106        }
2107
2108        panel.update(cx, |_, cx| {
2109            // Wait to create a new context until the workspace is no longer
2110            // being updated.
2111            cx.defer_in(window, move |panel, window, cx| {
2112                if panel.has_active_thread() {
2113                    panel.message_editor.update(cx, |message_editor, cx| {
2114                        message_editor.context_store().update(cx, |store, cx| {
2115                            let buffer = buffer.read(cx);
2116                            let selection_ranges = selection_ranges
2117                                .into_iter()
2118                                .flat_map(|range| {
2119                                    let (start_buffer, start) =
2120                                        buffer.text_anchor_for_position(range.start, cx)?;
2121                                    let (end_buffer, end) =
2122                                        buffer.text_anchor_for_position(range.end, cx)?;
2123                                    if start_buffer != end_buffer {
2124                                        return None;
2125                                    }
2126                                    Some((start_buffer, start..end))
2127                                })
2128                                .collect::<Vec<_>>();
2129
2130                            for (buffer, range) in selection_ranges {
2131                                store.add_selection(buffer, range, cx);
2132                            }
2133                        })
2134                    })
2135                } else if let Some(context_editor) = panel.active_context_editor() {
2136                    let snapshot = buffer.read(cx).snapshot(cx);
2137                    let selection_ranges = selection_ranges
2138                        .into_iter()
2139                        .map(|range| range.to_point(&snapshot))
2140                        .collect::<Vec<_>>();
2141
2142                    context_editor.update(cx, |context_editor, cx| {
2143                        context_editor.quote_ranges(selection_ranges, snapshot, window, cx)
2144                    });
2145                }
2146            });
2147        });
2148    }
2149}