assistant_panel.rs

   1use crate::Assistant;
   2use crate::assistant_configuration::{ConfigurationView, ConfigurationViewEvent};
   3use crate::{
   4    DeployHistory, InlineAssistant, NewChat, terminal_inline_assistant::TerminalInlineAssistant,
   5};
   6use anyhow::{Result, anyhow};
   7use assistant_context_editor::{
   8    AssistantContext, AssistantPanelDelegate, ContextEditor, ContextEditorToolbarItem,
   9    ContextEditorToolbarItemEvent, ContextHistory, ContextId, ContextStore, ContextStoreEvent,
  10    DEFAULT_TAB_TITLE, InsertDraggedFiles, SlashCommandCompletionProvider,
  11    make_lsp_adapter_delegate,
  12};
  13use assistant_settings::{AssistantDockPosition, AssistantSettings};
  14use assistant_slash_command::SlashCommandWorkingSet;
  15use client::{Client, Status, proto};
  16use editor::{Anchor, AnchorRangeExt as _, Editor, EditorEvent, MultiBuffer};
  17use fs::Fs;
  18use gpui::{
  19    Action, App, AsyncWindowContext, Entity, EventEmitter, ExternalPaths, FocusHandle, Focusable,
  20    InteractiveElement, IntoElement, ParentElement, Pixels, Render, Styled, Subscription, Task,
  21    UpdateGlobal, WeakEntity, prelude::*,
  22};
  23use language::LanguageRegistry;
  24use language_model::{
  25    AuthenticateError, ConfiguredModel, LanguageModelProviderId, LanguageModelRegistry,
  26};
  27use project::Project;
  28use prompt_store::{PromptBuilder, UserPromptId};
  29use rules_library::{RulesLibrary, open_rules_library};
  30
  31use search::{BufferSearchBar, buffer_search::DivRegistrar};
  32use settings::{Settings, update_settings_file};
  33use smol::stream::StreamExt;
  34
  35use std::ops::Range;
  36use std::path::Path;
  37use std::{ops::ControlFlow, sync::Arc};
  38use terminal_view::{TerminalView, terminal_panel::TerminalPanel};
  39use ui::{ContextMenu, PopoverMenu, Tooltip, prelude::*};
  40use util::{ResultExt, maybe};
  41use workspace::DraggedTab;
  42use workspace::{
  43    DraggedSelection, Pane, ToggleZoom, Workspace,
  44    dock::{DockPosition, Panel, PanelEvent},
  45    pane,
  46};
  47use zed_actions::assistant::{InlineAssist, OpenRulesLibrary, ShowConfiguration, ToggleFocus};
  48
  49pub fn init(cx: &mut App) {
  50    workspace::FollowableViewRegistry::register::<ContextEditor>(cx);
  51    cx.observe_new(
  52        |workspace: &mut Workspace, _window, _cx: &mut Context<Workspace>| {
  53            workspace
  54                .register_action(ContextEditor::quote_selection)
  55                .register_action(ContextEditor::insert_selection)
  56                .register_action(ContextEditor::copy_code)
  57                .register_action(ContextEditor::handle_insert_dragged_files)
  58                .register_action(AssistantPanel::show_configuration)
  59                .register_action(AssistantPanel::create_new_context)
  60                .register_action(AssistantPanel::restart_context_servers)
  61                .register_action(|workspace, action: &OpenRulesLibrary, window, cx| {
  62                    if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
  63                        workspace.focus_panel::<AssistantPanel>(window, cx);
  64                        panel.update(cx, |panel, cx| {
  65                            panel.deploy_rules_library(action, window, cx)
  66                        });
  67                    }
  68                });
  69        },
  70    )
  71    .detach();
  72
  73    cx.observe_new(
  74        |terminal_panel: &mut TerminalPanel, _, cx: &mut Context<TerminalPanel>| {
  75            terminal_panel.set_assistant_enabled(Assistant::enabled(cx), cx);
  76        },
  77    )
  78    .detach();
  79}
  80
  81pub enum AssistantPanelEvent {
  82    ContextEdited,
  83}
  84
  85pub struct AssistantPanel {
  86    pane: Entity<Pane>,
  87    workspace: WeakEntity<Workspace>,
  88    width: Option<Pixels>,
  89    height: Option<Pixels>,
  90    project: Entity<Project>,
  91    context_store: Entity<ContextStore>,
  92    languages: Arc<LanguageRegistry>,
  93    fs: Arc<dyn Fs>,
  94    subscriptions: Vec<Subscription>,
  95    model_summary_editor: Entity<Editor>,
  96    authenticate_provider_task: Option<(LanguageModelProviderId, Task<()>)>,
  97    configuration_subscription: Option<Subscription>,
  98    client_status: Option<client::Status>,
  99    watch_client_status: Option<Task<()>>,
 100    pub(crate) show_zed_ai_notice: bool,
 101}
 102
 103enum InlineAssistTarget {
 104    Editor(Entity<Editor>, bool),
 105    Terminal(Entity<TerminalView>),
 106}
 107
 108impl AssistantPanel {
 109    pub fn load(
 110        workspace: WeakEntity<Workspace>,
 111        prompt_builder: Arc<PromptBuilder>,
 112        cx: AsyncWindowContext,
 113    ) -> Task<Result<Entity<Self>>> {
 114        cx.spawn(async move |cx| {
 115            let slash_commands = Arc::new(SlashCommandWorkingSet::default());
 116            let context_store = workspace
 117                .update(cx, |workspace, cx| {
 118                    let project = workspace.project().clone();
 119                    ContextStore::new(project, prompt_builder.clone(), slash_commands, cx)
 120                })?
 121                .await?;
 122
 123            workspace.update_in(cx, |workspace, window, cx| {
 124                // TODO: deserialize state.
 125                cx.new(|cx| Self::new(workspace, context_store, window, cx))
 126            })
 127        })
 128    }
 129
 130    fn new(
 131        workspace: &Workspace,
 132        context_store: Entity<ContextStore>,
 133        window: &mut Window,
 134        cx: &mut Context<Self>,
 135    ) -> Self {
 136        let model_summary_editor = cx.new(|cx| Editor::single_line(window, cx));
 137        let context_editor_toolbar =
 138            cx.new(|_| ContextEditorToolbarItem::new(model_summary_editor.clone()));
 139
 140        let pane = cx.new(|cx| {
 141            let mut pane = Pane::new(
 142                workspace.weak_handle(),
 143                workspace.project().clone(),
 144                Default::default(),
 145                None,
 146                NewChat.boxed_clone(),
 147                window,
 148                cx,
 149            );
 150
 151            let project = workspace.project().clone();
 152            pane.set_custom_drop_handle(cx, move |_, dropped_item, window, cx| {
 153                let action = maybe!({
 154                    if project.read(cx).is_local() {
 155                        if let Some(paths) = dropped_item.downcast_ref::<ExternalPaths>() {
 156                            return Some(InsertDraggedFiles::ExternalFiles(paths.paths().to_vec()));
 157                        }
 158                    }
 159
 160                    let project_paths = if let Some(tab) = dropped_item.downcast_ref::<DraggedTab>()
 161                    {
 162                        if tab.pane == cx.entity() {
 163                            return None;
 164                        }
 165                        let item = tab.pane.read(cx).item_for_index(tab.ix);
 166                        Some(
 167                            item.and_then(|item| item.project_path(cx))
 168                                .into_iter()
 169                                .collect::<Vec<_>>(),
 170                        )
 171                    } else if let Some(selection) = dropped_item.downcast_ref::<DraggedSelection>()
 172                    {
 173                        Some(
 174                            selection
 175                                .items()
 176                                .filter_map(|item| {
 177                                    project.read(cx).path_for_entry(item.entry_id, cx)
 178                                })
 179                                .collect::<Vec<_>>(),
 180                        )
 181                    } else {
 182                        None
 183                    }?;
 184
 185                    Some(InsertDraggedFiles::ProjectPaths(project_paths))
 186                });
 187
 188                if let Some(action) = action {
 189                    window.dispatch_action(action.boxed_clone(), cx);
 190                }
 191
 192                ControlFlow::Break(())
 193            });
 194
 195            pane.set_can_navigate(true, cx);
 196            pane.display_nav_history_buttons(None);
 197            pane.set_should_display_tab_bar(|_, _| true);
 198            pane.set_render_tab_bar_buttons(cx, move |pane, _window, cx| {
 199                let focus_handle = pane.focus_handle(cx);
 200                let left_children = IconButton::new("history", IconName::HistoryRerun)
 201                    .icon_size(IconSize::Small)
 202                    .on_click(cx.listener({
 203                        let focus_handle = focus_handle.clone();
 204                        move |_, _, window, cx| {
 205                            focus_handle.focus(window);
 206                            window.dispatch_action(DeployHistory.boxed_clone(), cx)
 207                        }
 208                    }))
 209                    .tooltip({
 210                        let focus_handle = focus_handle.clone();
 211                        move |window, cx| {
 212                            Tooltip::for_action_in(
 213                                "Open History",
 214                                &DeployHistory,
 215                                &focus_handle,
 216                                window,
 217                                cx,
 218                            )
 219                        }
 220                    })
 221                    .toggle_state(
 222                        pane.active_item()
 223                            .map_or(false, |item| item.downcast::<ContextHistory>().is_some()),
 224                    );
 225                let _pane = cx.entity().clone();
 226                let right_children = h_flex()
 227                    .gap(DynamicSpacing::Base02.rems(cx))
 228                    .child(
 229                        IconButton::new("new-chat", IconName::Plus)
 230                            .icon_size(IconSize::Small)
 231                            .on_click(cx.listener(|_, _, window, cx| {
 232                                window.dispatch_action(NewChat.boxed_clone(), cx)
 233                            }))
 234                            .tooltip(move |window, cx| {
 235                                Tooltip::for_action_in(
 236                                    "New Chat",
 237                                    &NewChat,
 238                                    &focus_handle,
 239                                    window,
 240                                    cx,
 241                                )
 242                            }),
 243                    )
 244                    .child(
 245                        PopoverMenu::new("assistant-panel-popover-menu")
 246                            .trigger_with_tooltip(
 247                                IconButton::new("menu", IconName::EllipsisVertical)
 248                                    .icon_size(IconSize::Small),
 249                                Tooltip::text("Toggle Assistant Menu"),
 250                            )
 251                            .menu(move |window, cx| {
 252                                let zoom_label = if _pane.read(cx).is_zoomed() {
 253                                    "Zoom Out"
 254                                } else {
 255                                    "Zoom In"
 256                                };
 257                                let focus_handle = _pane.focus_handle(cx);
 258                                Some(ContextMenu::build(window, cx, move |menu, _, _| {
 259                                    menu.context(focus_handle.clone())
 260                                        .action("New Chat", Box::new(NewChat))
 261                                        .action("History", Box::new(DeployHistory))
 262                                        .action(
 263                                            "Rules Library",
 264                                            Box::new(OpenRulesLibrary::default()),
 265                                        )
 266                                        .action("Configure", Box::new(ShowConfiguration))
 267                                        .action(zoom_label, Box::new(ToggleZoom))
 268                                }))
 269                            }),
 270                    )
 271                    .into_any_element()
 272                    .into();
 273
 274                (Some(left_children.into_any_element()), right_children)
 275            });
 276            pane.toolbar().update(cx, |toolbar, cx| {
 277                toolbar.add_item(context_editor_toolbar.clone(), window, cx);
 278                toolbar.add_item(
 279                    cx.new(|cx| {
 280                        BufferSearchBar::new(
 281                            Some(workspace.project().read(cx).languages().clone()),
 282                            window,
 283                            cx,
 284                        )
 285                    }),
 286                    window,
 287                    cx,
 288                )
 289            });
 290            pane
 291        });
 292
 293        let subscriptions = vec![
 294            cx.observe(&pane, |_, _, cx| cx.notify()),
 295            cx.subscribe_in(&pane, window, Self::handle_pane_event),
 296            cx.subscribe(&context_editor_toolbar, Self::handle_toolbar_event),
 297            cx.subscribe(&model_summary_editor, Self::handle_summary_editor_event),
 298            cx.subscribe_in(&context_store, window, Self::handle_context_store_event),
 299            cx.subscribe_in(
 300                &LanguageModelRegistry::global(cx),
 301                window,
 302                |this, _, event: &language_model::Event, window, cx| match event {
 303                    language_model::Event::DefaultModelChanged
 304                    | language_model::Event::InlineAssistantModelChanged
 305                    | language_model::Event::CommitMessageModelChanged
 306                    | language_model::Event::ThreadSummaryModelChanged => {
 307                        this.completion_provider_changed(window, cx);
 308                    }
 309                    language_model::Event::ProviderStateChanged => {
 310                        this.ensure_authenticated(window, cx);
 311                        cx.notify()
 312                    }
 313                    language_model::Event::AddedProvider(_)
 314                    | language_model::Event::RemovedProvider(_) => {
 315                        this.ensure_authenticated(window, cx);
 316                    }
 317                },
 318            ),
 319        ];
 320
 321        let watch_client_status = Self::watch_client_status(workspace.client().clone(), window, cx);
 322
 323        let mut this = Self {
 324            pane,
 325            workspace: workspace.weak_handle(),
 326            width: None,
 327            height: None,
 328            project: workspace.project().clone(),
 329            context_store,
 330            languages: workspace.app_state().languages.clone(),
 331            fs: workspace.app_state().fs.clone(),
 332            subscriptions,
 333            model_summary_editor,
 334            authenticate_provider_task: None,
 335            configuration_subscription: None,
 336            client_status: None,
 337            watch_client_status: Some(watch_client_status),
 338            show_zed_ai_notice: false,
 339        };
 340        this.new_context(window, cx);
 341        this
 342    }
 343
 344    pub fn toggle_focus(
 345        workspace: &mut Workspace,
 346        _: &ToggleFocus,
 347        window: &mut Window,
 348        cx: &mut Context<Workspace>,
 349    ) {
 350        if workspace
 351            .panel::<Self>(cx)
 352            .is_some_and(|panel| panel.read(cx).enabled(cx))
 353        {
 354            workspace.toggle_panel_focus::<Self>(window, cx);
 355        }
 356    }
 357
 358    fn watch_client_status(
 359        client: Arc<Client>,
 360        window: &mut Window,
 361        cx: &mut Context<Self>,
 362    ) -> Task<()> {
 363        let mut status_rx = client.status();
 364
 365        cx.spawn_in(window, async move |this, cx| {
 366            while let Some(status) = status_rx.next().await {
 367                this.update(cx, |this, cx| {
 368                    if this.client_status.is_none()
 369                        || this
 370                            .client_status
 371                            .map_or(false, |old_status| old_status != status)
 372                    {
 373                        this.update_zed_ai_notice_visibility(status, cx);
 374                    }
 375                    this.client_status = Some(status);
 376                })
 377                .log_err();
 378            }
 379            this.update(cx, |this, _cx| this.watch_client_status = None)
 380                .log_err();
 381        })
 382    }
 383
 384    fn handle_pane_event(
 385        &mut self,
 386        pane: &Entity<Pane>,
 387        event: &pane::Event,
 388        window: &mut Window,
 389        cx: &mut Context<Self>,
 390    ) {
 391        let update_model_summary = match event {
 392            pane::Event::Remove { .. } => {
 393                cx.emit(PanelEvent::Close);
 394                false
 395            }
 396            pane::Event::ZoomIn => {
 397                cx.emit(PanelEvent::ZoomIn);
 398                false
 399            }
 400            pane::Event::ZoomOut => {
 401                cx.emit(PanelEvent::ZoomOut);
 402                false
 403            }
 404
 405            pane::Event::AddItem { item } => {
 406                self.workspace
 407                    .update(cx, |workspace, cx| {
 408                        item.added_to_pane(workspace, self.pane.clone(), window, cx)
 409                    })
 410                    .ok();
 411                true
 412            }
 413
 414            pane::Event::ActivateItem { local, .. } => {
 415                if *local {
 416                    self.workspace
 417                        .update(cx, |workspace, cx| {
 418                            workspace.unfollow_in_pane(&pane, window, cx);
 419                        })
 420                        .ok();
 421                }
 422                cx.emit(AssistantPanelEvent::ContextEdited);
 423                true
 424            }
 425            pane::Event::RemovedItem { .. } => {
 426                let has_configuration_view = self
 427                    .pane
 428                    .read(cx)
 429                    .items_of_type::<ConfigurationView>()
 430                    .next()
 431                    .is_some();
 432
 433                if !has_configuration_view {
 434                    self.configuration_subscription = None;
 435                }
 436
 437                cx.emit(AssistantPanelEvent::ContextEdited);
 438                true
 439            }
 440
 441            _ => false,
 442        };
 443
 444        if update_model_summary {
 445            if let Some(editor) = self.active_context_editor(cx) {
 446                self.show_updated_summary(&editor, window, cx)
 447            }
 448        }
 449    }
 450
 451    fn handle_summary_editor_event(
 452        &mut self,
 453        model_summary_editor: Entity<Editor>,
 454        event: &EditorEvent,
 455        cx: &mut Context<Self>,
 456    ) {
 457        if matches!(event, EditorEvent::Edited { .. }) {
 458            if let Some(context_editor) = self.active_context_editor(cx) {
 459                let new_summary = model_summary_editor.read(cx).text(cx);
 460                context_editor.update(cx, |context_editor, cx| {
 461                    context_editor.context().update(cx, |context, cx| {
 462                        if context.summary().is_none()
 463                            && (new_summary == DEFAULT_TAB_TITLE || new_summary.trim().is_empty())
 464                        {
 465                            return;
 466                        }
 467                        context.set_custom_summary(new_summary, cx)
 468                    });
 469                });
 470            }
 471        }
 472    }
 473
 474    fn update_zed_ai_notice_visibility(&mut self, client_status: Status, cx: &mut Context<Self>) {
 475        let model = LanguageModelRegistry::read_global(cx).default_model();
 476
 477        // If we're signed out and don't have a provider configured, or we're signed-out AND Zed.dev is
 478        // the provider, we want to show a nudge to sign in.
 479        let show_zed_ai_notice =
 480            client_status.is_signed_out() && model.map_or(true, |model| model.is_provided_by_zed());
 481
 482        self.show_zed_ai_notice = show_zed_ai_notice;
 483        cx.notify();
 484    }
 485
 486    fn handle_toolbar_event(
 487        &mut self,
 488        _: Entity<ContextEditorToolbarItem>,
 489        _: &ContextEditorToolbarItemEvent,
 490        cx: &mut Context<Self>,
 491    ) {
 492        if let Some(context_editor) = self.active_context_editor(cx) {
 493            context_editor.update(cx, |context_editor, cx| {
 494                context_editor.context().update(cx, |context, cx| {
 495                    context.summarize(true, cx);
 496                })
 497            })
 498        }
 499    }
 500
 501    fn handle_context_store_event(
 502        &mut self,
 503        _context_store: &Entity<ContextStore>,
 504        event: &ContextStoreEvent,
 505        window: &mut Window,
 506        cx: &mut Context<Self>,
 507    ) {
 508        let ContextStoreEvent::ContextCreated(context_id) = event;
 509        let Some(context) = self
 510            .context_store
 511            .read(cx)
 512            .loaded_context_for_id(&context_id, cx)
 513        else {
 514            log::error!("no context found with ID: {}", context_id.to_proto());
 515            return;
 516        };
 517        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 518            .log_err()
 519            .flatten();
 520
 521        let editor = cx.new(|cx| {
 522            let mut editor = ContextEditor::for_context(
 523                context,
 524                self.fs.clone(),
 525                self.workspace.clone(),
 526                self.project.clone(),
 527                lsp_adapter_delegate,
 528                window,
 529                cx,
 530            );
 531            editor.insert_default_prompt(window, cx);
 532            editor
 533        });
 534
 535        self.show_context(editor.clone(), window, cx);
 536    }
 537
 538    fn completion_provider_changed(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 539        if let Some(editor) = self.active_context_editor(cx) {
 540            editor.update(cx, |active_context, cx| {
 541                active_context
 542                    .context()
 543                    .update(cx, |context, cx| context.completion_provider_changed(cx))
 544            })
 545        }
 546
 547        let Some(new_provider_id) = LanguageModelRegistry::read_global(cx)
 548            .default_model()
 549            .map(|default| default.provider.id())
 550        else {
 551            return;
 552        };
 553
 554        if self
 555            .authenticate_provider_task
 556            .as_ref()
 557            .map_or(true, |(old_provider_id, _)| {
 558                *old_provider_id != new_provider_id
 559            })
 560        {
 561            self.authenticate_provider_task = None;
 562            self.ensure_authenticated(window, cx);
 563        }
 564
 565        if let Some(status) = self.client_status {
 566            self.update_zed_ai_notice_visibility(status, cx);
 567        }
 568    }
 569
 570    fn ensure_authenticated(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 571        if self.is_authenticated(cx) {
 572            return;
 573        }
 574
 575        let Some(ConfiguredModel { provider, .. }) =
 576            LanguageModelRegistry::read_global(cx).default_model()
 577        else {
 578            return;
 579        };
 580
 581        let load_credentials = self.authenticate(cx);
 582
 583        if self.authenticate_provider_task.is_none() {
 584            self.authenticate_provider_task = Some((
 585                provider.id(),
 586                cx.spawn_in(window, async move |this, cx| {
 587                    if let Some(future) = load_credentials {
 588                        let _ = future.await;
 589                    }
 590                    this.update(cx, |this, _cx| {
 591                        this.authenticate_provider_task = None;
 592                    })
 593                    .log_err();
 594                }),
 595            ));
 596        }
 597    }
 598
 599    pub fn inline_assist(
 600        workspace: &mut Workspace,
 601        action: &InlineAssist,
 602        window: &mut Window,
 603        cx: &mut Context<Workspace>,
 604    ) {
 605        let Some(assistant_panel) = workspace
 606            .panel::<AssistantPanel>(cx)
 607            .filter(|panel| panel.read(cx).enabled(cx))
 608        else {
 609            return;
 610        };
 611
 612        let Some(inline_assist_target) =
 613            Self::resolve_inline_assist_target(workspace, &assistant_panel, window, cx)
 614        else {
 615            return;
 616        };
 617
 618        let initial_prompt = action.prompt.clone();
 619
 620        if assistant_panel.update(cx, |assistant, cx| assistant.is_authenticated(cx)) {
 621            match inline_assist_target {
 622                InlineAssistTarget::Editor(active_editor, include_context) => {
 623                    InlineAssistant::update_global(cx, |assistant, cx| {
 624                        assistant.assist(
 625                            &active_editor,
 626                            Some(cx.entity().downgrade()),
 627                            include_context.then_some(&assistant_panel),
 628                            initial_prompt,
 629                            window,
 630                            cx,
 631                        )
 632                    })
 633                }
 634                InlineAssistTarget::Terminal(active_terminal) => {
 635                    TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 636                        assistant.assist(
 637                            &active_terminal,
 638                            Some(cx.entity().downgrade()),
 639                            Some(&assistant_panel),
 640                            initial_prompt,
 641                            window,
 642                            cx,
 643                        )
 644                    })
 645                }
 646            }
 647        } else {
 648            let assistant_panel = assistant_panel.downgrade();
 649            cx.spawn_in(window, async move |workspace, cx| {
 650                let Some(task) =
 651                    assistant_panel.update(cx, |assistant, cx| assistant.authenticate(cx))?
 652                else {
 653                    let answer = cx
 654                        .prompt(
 655                            gpui::PromptLevel::Warning,
 656                            "No language model provider configured",
 657                            None,
 658                            &["Configure", "Cancel"],
 659                        )
 660                        .await
 661                        .ok();
 662                    if let Some(answer) = answer {
 663                        if answer == 0 {
 664                            cx.update(|window, cx| {
 665                                window.dispatch_action(Box::new(ShowConfiguration), cx)
 666                            })
 667                            .ok();
 668                        }
 669                    }
 670                    return Ok(());
 671                };
 672                task.await?;
 673                if assistant_panel.update(cx, |panel, cx| panel.is_authenticated(cx))? {
 674                    cx.update(|window, cx| match inline_assist_target {
 675                        InlineAssistTarget::Editor(active_editor, include_context) => {
 676                            let assistant_panel = if include_context {
 677                                assistant_panel.upgrade()
 678                            } else {
 679                                None
 680                            };
 681                            InlineAssistant::update_global(cx, |assistant, cx| {
 682                                assistant.assist(
 683                                    &active_editor,
 684                                    Some(workspace),
 685                                    assistant_panel.as_ref(),
 686                                    initial_prompt,
 687                                    window,
 688                                    cx,
 689                                )
 690                            })
 691                        }
 692                        InlineAssistTarget::Terminal(active_terminal) => {
 693                            TerminalInlineAssistant::update_global(cx, |assistant, cx| {
 694                                assistant.assist(
 695                                    &active_terminal,
 696                                    Some(workspace),
 697                                    assistant_panel.upgrade().as_ref(),
 698                                    initial_prompt,
 699                                    window,
 700                                    cx,
 701                                )
 702                            })
 703                        }
 704                    })?
 705                } else {
 706                    workspace.update_in(cx, |workspace, window, cx| {
 707                        workspace.focus_panel::<AssistantPanel>(window, cx)
 708                    })?;
 709                }
 710
 711                anyhow::Ok(())
 712            })
 713            .detach_and_log_err(cx)
 714        }
 715    }
 716
 717    fn resolve_inline_assist_target(
 718        workspace: &mut Workspace,
 719        assistant_panel: &Entity<AssistantPanel>,
 720        window: &mut Window,
 721        cx: &mut App,
 722    ) -> Option<InlineAssistTarget> {
 723        if let Some(terminal_panel) = workspace.panel::<TerminalPanel>(cx) {
 724            if terminal_panel
 725                .read(cx)
 726                .focus_handle(cx)
 727                .contains_focused(window, cx)
 728            {
 729                if let Some(terminal_view) = terminal_panel.read(cx).pane().and_then(|pane| {
 730                    pane.read(cx)
 731                        .active_item()
 732                        .and_then(|t| t.downcast::<TerminalView>())
 733                }) {
 734                    return Some(InlineAssistTarget::Terminal(terminal_view));
 735                }
 736            }
 737        }
 738        let context_editor =
 739            assistant_panel
 740                .read(cx)
 741                .active_context_editor(cx)
 742                .and_then(|editor| {
 743                    let editor = &editor.read(cx).editor().clone();
 744                    if editor.read(cx).is_focused(window) {
 745                        Some(editor.clone())
 746                    } else {
 747                        None
 748                    }
 749                });
 750
 751        if let Some(context_editor) = context_editor {
 752            Some(InlineAssistTarget::Editor(context_editor, false))
 753        } else if let Some(workspace_editor) = workspace
 754            .active_item(cx)
 755            .and_then(|item| item.act_as::<Editor>(cx))
 756        {
 757            Some(InlineAssistTarget::Editor(workspace_editor, true))
 758        } else if let Some(terminal_view) = workspace
 759            .active_item(cx)
 760            .and_then(|item| item.act_as::<TerminalView>(cx))
 761        {
 762            Some(InlineAssistTarget::Terminal(terminal_view))
 763        } else {
 764            None
 765        }
 766    }
 767
 768    pub fn create_new_context(
 769        workspace: &mut Workspace,
 770        _: &NewChat,
 771        window: &mut Window,
 772        cx: &mut Context<Workspace>,
 773    ) {
 774        if let Some(panel) = workspace.panel::<AssistantPanel>(cx) {
 775            let did_create_context = panel
 776                .update(cx, |panel, cx| {
 777                    panel.new_context(window, cx)?;
 778
 779                    Some(())
 780                })
 781                .is_some();
 782            if did_create_context {
 783                ContextEditor::quote_selection(workspace, &Default::default(), window, cx);
 784            }
 785        }
 786    }
 787
 788    pub fn new_context(
 789        &mut self,
 790        window: &mut Window,
 791        cx: &mut Context<Self>,
 792    ) -> Option<Entity<ContextEditor>> {
 793        let project = self.project.read(cx);
 794        if project.is_via_collab() {
 795            let task = self
 796                .context_store
 797                .update(cx, |store, cx| store.create_remote_context(cx));
 798
 799            cx.spawn_in(window, async move |this, cx| {
 800                let context = task.await?;
 801
 802                this.update_in(cx, |this, window, cx| {
 803                    let workspace = this.workspace.clone();
 804                    let project = this.project.clone();
 805                    let lsp_adapter_delegate =
 806                        make_lsp_adapter_delegate(&project, cx).log_err().flatten();
 807
 808                    let fs = this.fs.clone();
 809                    let project = this.project.clone();
 810
 811                    let editor = cx.new(|cx| {
 812                        ContextEditor::for_context(
 813                            context,
 814                            fs,
 815                            workspace,
 816                            project,
 817                            lsp_adapter_delegate,
 818                            window,
 819                            cx,
 820                        )
 821                    });
 822
 823                    this.show_context(editor, window, cx);
 824
 825                    anyhow::Ok(())
 826                })??;
 827
 828                anyhow::Ok(())
 829            })
 830            .detach_and_log_err(cx);
 831
 832            None
 833        } else {
 834            let context = self.context_store.update(cx, |store, cx| store.create(cx));
 835            let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
 836                .log_err()
 837                .flatten();
 838
 839            let editor = cx.new(|cx| {
 840                let mut editor = ContextEditor::for_context(
 841                    context,
 842                    self.fs.clone(),
 843                    self.workspace.clone(),
 844                    self.project.clone(),
 845                    lsp_adapter_delegate,
 846                    window,
 847                    cx,
 848                );
 849                editor.insert_default_prompt(window, cx);
 850                editor
 851            });
 852
 853            self.show_context(editor.clone(), window, cx);
 854            let workspace = self.workspace.clone();
 855            cx.spawn_in(window, async move |_, cx| {
 856                workspace
 857                    .update_in(cx, |workspace, window, cx| {
 858                        workspace.focus_panel::<AssistantPanel>(window, cx);
 859                    })
 860                    .ok();
 861            })
 862            .detach();
 863            Some(editor)
 864        }
 865    }
 866
 867    fn show_context(
 868        &mut self,
 869        context_editor: Entity<ContextEditor>,
 870        window: &mut Window,
 871        cx: &mut Context<Self>,
 872    ) {
 873        let focus = self.focus_handle(cx).contains_focused(window, cx);
 874        let prev_len = self.pane.read(cx).items_len();
 875        self.pane.update(cx, |pane, cx| {
 876            pane.add_item(
 877                Box::new(context_editor.clone()),
 878                focus,
 879                focus,
 880                None,
 881                window,
 882                cx,
 883            )
 884        });
 885
 886        if prev_len != self.pane.read(cx).items_len() {
 887            self.subscriptions.push(cx.subscribe_in(
 888                &context_editor,
 889                window,
 890                Self::handle_context_editor_event,
 891            ));
 892        }
 893
 894        self.show_updated_summary(&context_editor, window, cx);
 895
 896        cx.emit(AssistantPanelEvent::ContextEdited);
 897        cx.notify();
 898    }
 899
 900    fn show_updated_summary(
 901        &self,
 902        context_editor: &Entity<ContextEditor>,
 903        window: &mut Window,
 904        cx: &mut Context<Self>,
 905    ) {
 906        context_editor.update(cx, |context_editor, cx| {
 907            let new_summary = context_editor.title(cx).to_string();
 908            self.model_summary_editor.update(cx, |summary_editor, cx| {
 909                if summary_editor.text(cx) != new_summary {
 910                    summary_editor.set_text(new_summary, window, cx);
 911                }
 912            });
 913        });
 914    }
 915
 916    fn handle_context_editor_event(
 917        &mut self,
 918        context_editor: &Entity<ContextEditor>,
 919        event: &EditorEvent,
 920        window: &mut Window,
 921        cx: &mut Context<Self>,
 922    ) {
 923        match event {
 924            EditorEvent::TitleChanged => {
 925                self.show_updated_summary(&context_editor, window, cx);
 926                cx.notify()
 927            }
 928            EditorEvent::Edited { .. } => {
 929                self.workspace
 930                    .update(cx, |workspace, cx| {
 931                        let is_via_ssh = workspace
 932                            .project()
 933                            .update(cx, |project, _| project.is_via_ssh());
 934
 935                        workspace
 936                            .client()
 937                            .telemetry()
 938                            .log_edit_event("assistant panel", is_via_ssh);
 939                    })
 940                    .log_err();
 941                cx.emit(AssistantPanelEvent::ContextEdited)
 942            }
 943            _ => {}
 944        }
 945    }
 946
 947    fn show_configuration(
 948        workspace: &mut Workspace,
 949        _: &ShowConfiguration,
 950        window: &mut Window,
 951        cx: &mut Context<Workspace>,
 952    ) {
 953        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
 954            return;
 955        };
 956
 957        if !panel.focus_handle(cx).contains_focused(window, cx) {
 958            workspace.toggle_panel_focus::<AssistantPanel>(window, cx);
 959        }
 960
 961        panel.update(cx, |this, cx| {
 962            this.show_configuration_tab(window, cx);
 963        })
 964    }
 965
 966    fn show_configuration_tab(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 967        let configuration_item_ix = self
 968            .pane
 969            .read(cx)
 970            .items()
 971            .position(|item| item.downcast::<ConfigurationView>().is_some());
 972
 973        if let Some(configuration_item_ix) = configuration_item_ix {
 974            self.pane.update(cx, |pane, cx| {
 975                pane.activate_item(configuration_item_ix, true, true, window, cx);
 976            });
 977        } else {
 978            let configuration = cx.new(|cx| ConfigurationView::new(window, cx));
 979            self.configuration_subscription = Some(cx.subscribe_in(
 980                &configuration,
 981                window,
 982                |this, _, event: &ConfigurationViewEvent, window, cx| match event {
 983                    ConfigurationViewEvent::NewProviderContextEditor(provider) => {
 984                        if LanguageModelRegistry::read_global(cx)
 985                            .default_model()
 986                            .map_or(true, |default| default.provider.id() != provider.id())
 987                        {
 988                            if let Some(model) = provider.default_model(cx) {
 989                                update_settings_file::<AssistantSettings>(
 990                                    this.fs.clone(),
 991                                    cx,
 992                                    move |settings, _| settings.set_model(model),
 993                                );
 994                            }
 995                        }
 996
 997                        this.new_context(window, cx);
 998                    }
 999                },
1000            ));
1001            self.pane.update(cx, |pane, cx| {
1002                pane.add_item(Box::new(configuration), true, true, None, window, cx);
1003            });
1004        }
1005    }
1006
1007    fn deploy_history(&mut self, _: &DeployHistory, window: &mut Window, cx: &mut Context<Self>) {
1008        let history_item_ix = self
1009            .pane
1010            .read(cx)
1011            .items()
1012            .position(|item| item.downcast::<ContextHistory>().is_some());
1013
1014        if let Some(history_item_ix) = history_item_ix {
1015            self.pane.update(cx, |pane, cx| {
1016                pane.activate_item(history_item_ix, true, true, window, cx);
1017            });
1018        } else {
1019            let history = cx.new(|cx| {
1020                ContextHistory::new(
1021                    self.project.clone(),
1022                    self.context_store.clone(),
1023                    self.workspace.clone(),
1024                    window,
1025                    cx,
1026                )
1027            });
1028            self.pane.update(cx, |pane, cx| {
1029                pane.add_item(Box::new(history), true, true, None, window, cx);
1030            });
1031        }
1032    }
1033
1034    fn deploy_rules_library(
1035        &mut self,
1036        action: &OpenRulesLibrary,
1037        _window: &mut Window,
1038        cx: &mut Context<Self>,
1039    ) {
1040        open_rules_library(
1041            self.languages.clone(),
1042            Box::new(PromptLibraryInlineAssist),
1043            Arc::new(|| {
1044                Box::new(SlashCommandCompletionProvider::new(
1045                    Arc::new(SlashCommandWorkingSet::default()),
1046                    None,
1047                    None,
1048                ))
1049            }),
1050            action
1051                .prompt_to_select
1052                .map(|uuid| UserPromptId(uuid).into()),
1053            cx,
1054        )
1055        .detach_and_log_err(cx);
1056    }
1057
1058    pub(crate) fn active_context_editor(&self, cx: &App) -> Option<Entity<ContextEditor>> {
1059        self.pane
1060            .read(cx)
1061            .active_item()?
1062            .downcast::<ContextEditor>()
1063    }
1064
1065    pub fn active_context(&self, cx: &App) -> Option<Entity<AssistantContext>> {
1066        Some(self.active_context_editor(cx)?.read(cx).context().clone())
1067    }
1068
1069    pub fn open_saved_context(
1070        &mut self,
1071        path: Arc<Path>,
1072        window: &mut Window,
1073        cx: &mut Context<Self>,
1074    ) -> Task<Result<()>> {
1075        let existing_context = self.pane.read(cx).items().find_map(|item| {
1076            item.downcast::<ContextEditor>()
1077                .filter(|editor| editor.read(cx).context().read(cx).path() == Some(&path))
1078        });
1079        if let Some(existing_context) = existing_context {
1080            return cx.spawn_in(window, async move |this, cx| {
1081                this.update_in(cx, |this, window, cx| {
1082                    this.show_context(existing_context, window, cx)
1083                })
1084            });
1085        }
1086
1087        let context = self
1088            .context_store
1089            .update(cx, |store, cx| store.open_local_context(path.clone(), cx));
1090        let fs = self.fs.clone();
1091        let project = self.project.clone();
1092        let workspace = self.workspace.clone();
1093
1094        let lsp_adapter_delegate = make_lsp_adapter_delegate(&project, cx).log_err().flatten();
1095
1096        cx.spawn_in(window, async move |this, cx| {
1097            let context = context.await?;
1098            this.update_in(cx, |this, window, cx| {
1099                let editor = cx.new(|cx| {
1100                    ContextEditor::for_context(
1101                        context,
1102                        fs,
1103                        workspace,
1104                        project,
1105                        lsp_adapter_delegate,
1106                        window,
1107                        cx,
1108                    )
1109                });
1110                this.show_context(editor, window, cx);
1111                anyhow::Ok(())
1112            })??;
1113            Ok(())
1114        })
1115    }
1116
1117    pub fn open_remote_context(
1118        &mut self,
1119        id: ContextId,
1120        window: &mut Window,
1121        cx: &mut Context<Self>,
1122    ) -> Task<Result<Entity<ContextEditor>>> {
1123        let existing_context = self.pane.read(cx).items().find_map(|item| {
1124            item.downcast::<ContextEditor>()
1125                .filter(|editor| *editor.read(cx).context().read(cx).id() == id)
1126        });
1127        if let Some(existing_context) = existing_context {
1128            return cx.spawn_in(window, async move |this, cx| {
1129                this.update_in(cx, |this, window, cx| {
1130                    this.show_context(existing_context.clone(), window, cx)
1131                })?;
1132                Ok(existing_context)
1133            });
1134        }
1135
1136        let context = self
1137            .context_store
1138            .update(cx, |store, cx| store.open_remote_context(id, cx));
1139        let fs = self.fs.clone();
1140        let workspace = self.workspace.clone();
1141        let lsp_adapter_delegate = make_lsp_adapter_delegate(&self.project, cx)
1142            .log_err()
1143            .flatten();
1144
1145        cx.spawn_in(window, async move |this, cx| {
1146            let context = context.await?;
1147            this.update_in(cx, |this, window, cx| {
1148                let editor = cx.new(|cx| {
1149                    ContextEditor::for_context(
1150                        context,
1151                        fs,
1152                        workspace,
1153                        this.project.clone(),
1154                        lsp_adapter_delegate,
1155                        window,
1156                        cx,
1157                    )
1158                });
1159                this.show_context(editor.clone(), window, cx);
1160                anyhow::Ok(editor)
1161            })?
1162        })
1163    }
1164
1165    fn is_authenticated(&mut self, cx: &mut Context<Self>) -> bool {
1166        LanguageModelRegistry::read_global(cx)
1167            .default_model()
1168            .map_or(false, |default| default.provider.is_authenticated(cx))
1169    }
1170
1171    fn authenticate(
1172        &mut self,
1173        cx: &mut Context<Self>,
1174    ) -> Option<Task<Result<(), AuthenticateError>>> {
1175        LanguageModelRegistry::read_global(cx)
1176            .default_model()
1177            .map_or(None, |default| Some(default.provider.authenticate(cx)))
1178    }
1179
1180    fn restart_context_servers(
1181        workspace: &mut Workspace,
1182        _action: &project::context_server_store::Restart,
1183        _: &mut Window,
1184        cx: &mut Context<Workspace>,
1185    ) {
1186        workspace
1187            .project()
1188            .read(cx)
1189            .context_server_store()
1190            .update(cx, |store, cx| {
1191                for server in store.running_servers() {
1192                    store.restart_server(&server.id(), cx).log_err();
1193                }
1194            });
1195    }
1196}
1197
1198impl Render for AssistantPanel {
1199    fn render(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1200        let mut registrar = DivRegistrar::new(
1201            |panel, _, cx| {
1202                panel
1203                    .pane
1204                    .read(cx)
1205                    .toolbar()
1206                    .read(cx)
1207                    .item_of_type::<BufferSearchBar>()
1208            },
1209            cx,
1210        );
1211        BufferSearchBar::register(&mut registrar);
1212        let registrar = registrar.into_div();
1213
1214        v_flex()
1215            .key_context("AssistantPanel")
1216            .size_full()
1217            .on_action(cx.listener(|this, _: &NewChat, window, cx| {
1218                this.new_context(window, cx);
1219            }))
1220            .on_action(cx.listener(|this, _: &ShowConfiguration, window, cx| {
1221                this.show_configuration_tab(window, cx)
1222            }))
1223            .on_action(cx.listener(AssistantPanel::deploy_history))
1224            .on_action(cx.listener(AssistantPanel::deploy_rules_library))
1225            .child(registrar.size_full().child(self.pane.clone()))
1226            .into_any_element()
1227    }
1228}
1229
1230impl Panel for AssistantPanel {
1231    fn persistent_name() -> &'static str {
1232        "AssistantPanel"
1233    }
1234
1235    fn position(&self, _: &Window, cx: &App) -> DockPosition {
1236        match AssistantSettings::get_global(cx).dock {
1237            AssistantDockPosition::Left => DockPosition::Left,
1238            AssistantDockPosition::Bottom => DockPosition::Bottom,
1239            AssistantDockPosition::Right => DockPosition::Right,
1240        }
1241    }
1242
1243    fn position_is_valid(&self, _: DockPosition) -> bool {
1244        true
1245    }
1246
1247    fn set_position(&mut self, position: DockPosition, _: &mut Window, cx: &mut Context<Self>) {
1248        settings::update_settings_file::<AssistantSettings>(
1249            self.fs.clone(),
1250            cx,
1251            move |settings, _| {
1252                let dock = match position {
1253                    DockPosition::Left => AssistantDockPosition::Left,
1254                    DockPosition::Bottom => AssistantDockPosition::Bottom,
1255                    DockPosition::Right => AssistantDockPosition::Right,
1256                };
1257                settings.set_dock(dock);
1258            },
1259        );
1260    }
1261
1262    fn size(&self, window: &Window, cx: &App) -> Pixels {
1263        let settings = AssistantSettings::get_global(cx);
1264        match self.position(window, cx) {
1265            DockPosition::Left | DockPosition::Right => {
1266                self.width.unwrap_or(settings.default_width)
1267            }
1268            DockPosition::Bottom => self.height.unwrap_or(settings.default_height),
1269        }
1270    }
1271
1272    fn set_size(&mut self, size: Option<Pixels>, window: &mut Window, cx: &mut Context<Self>) {
1273        match self.position(window, cx) {
1274            DockPosition::Left | DockPosition::Right => self.width = size,
1275            DockPosition::Bottom => self.height = size,
1276        }
1277        cx.notify();
1278    }
1279
1280    fn is_zoomed(&self, _: &Window, cx: &App) -> bool {
1281        self.pane.read(cx).is_zoomed()
1282    }
1283
1284    fn set_zoomed(&mut self, zoomed: bool, _: &mut Window, cx: &mut Context<Self>) {
1285        self.pane.update(cx, |pane, cx| pane.set_zoomed(zoomed, cx));
1286    }
1287
1288    fn set_active(&mut self, active: bool, window: &mut Window, cx: &mut Context<Self>) {
1289        if active {
1290            if self.pane.read(cx).items_len() == 0 {
1291                self.new_context(window, cx);
1292            }
1293
1294            self.ensure_authenticated(window, cx);
1295        }
1296    }
1297
1298    fn pane(&self) -> Option<Entity<Pane>> {
1299        Some(self.pane.clone())
1300    }
1301
1302    fn remote_id() -> Option<proto::PanelId> {
1303        Some(proto::PanelId::AssistantPanel)
1304    }
1305
1306    fn icon(&self, _: &Window, cx: &App) -> Option<IconName> {
1307        (self.enabled(cx) && AssistantSettings::get_global(cx).button)
1308            .then_some(IconName::ZedAssistant)
1309    }
1310
1311    fn icon_tooltip(&self, _: &Window, _: &App) -> Option<&'static str> {
1312        Some("Assistant Panel")
1313    }
1314
1315    fn toggle_action(&self) -> Box<dyn Action> {
1316        Box::new(ToggleFocus)
1317    }
1318
1319    fn activation_priority(&self) -> u32 {
1320        4
1321    }
1322
1323    fn enabled(&self, cx: &App) -> bool {
1324        Assistant::enabled(cx)
1325    }
1326}
1327
1328impl EventEmitter<PanelEvent> for AssistantPanel {}
1329impl EventEmitter<AssistantPanelEvent> for AssistantPanel {}
1330
1331impl Focusable for AssistantPanel {
1332    fn focus_handle(&self, cx: &App) -> FocusHandle {
1333        self.pane.focus_handle(cx)
1334    }
1335}
1336
1337struct PromptLibraryInlineAssist;
1338
1339impl rules_library::InlineAssistDelegate for PromptLibraryInlineAssist {
1340    fn assist(
1341        &self,
1342        prompt_editor: &Entity<Editor>,
1343        initial_prompt: Option<String>,
1344        window: &mut Window,
1345        cx: &mut Context<RulesLibrary>,
1346    ) {
1347        InlineAssistant::update_global(cx, |assistant, cx| {
1348            assistant.assist(&prompt_editor, None, None, initial_prompt, window, cx)
1349        })
1350    }
1351
1352    fn focus_assistant_panel(
1353        &self,
1354        workspace: &mut Workspace,
1355        window: &mut Window,
1356        cx: &mut Context<Workspace>,
1357    ) -> bool {
1358        workspace
1359            .focus_panel::<AssistantPanel>(window, cx)
1360            .is_some()
1361    }
1362}
1363
1364pub struct ConcreteAssistantPanelDelegate;
1365
1366impl AssistantPanelDelegate for ConcreteAssistantPanelDelegate {
1367    fn active_context_editor(
1368        &self,
1369        workspace: &mut Workspace,
1370        _window: &mut Window,
1371        cx: &mut Context<Workspace>,
1372    ) -> Option<Entity<ContextEditor>> {
1373        let panel = workspace.panel::<AssistantPanel>(cx)?;
1374        panel.read(cx).active_context_editor(cx)
1375    }
1376
1377    fn open_saved_context(
1378        &self,
1379        workspace: &mut Workspace,
1380        path: Arc<Path>,
1381        window: &mut Window,
1382        cx: &mut Context<Workspace>,
1383    ) -> Task<Result<()>> {
1384        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
1385            return Task::ready(Err(anyhow!("no Assistant panel found")));
1386        };
1387
1388        panel.update(cx, |panel, cx| panel.open_saved_context(path, window, cx))
1389    }
1390
1391    fn open_remote_context(
1392        &self,
1393        workspace: &mut Workspace,
1394        context_id: ContextId,
1395        window: &mut Window,
1396        cx: &mut Context<Workspace>,
1397    ) -> Task<Result<Entity<ContextEditor>>> {
1398        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
1399            return Task::ready(Err(anyhow!("no Assistant panel found")));
1400        };
1401
1402        panel.update(cx, |panel, cx| {
1403            panel.open_remote_context(context_id, window, cx)
1404        })
1405    }
1406
1407    fn quote_selection(
1408        &self,
1409        workspace: &mut Workspace,
1410        selection_ranges: Vec<Range<Anchor>>,
1411        buffer: Entity<MultiBuffer>,
1412        window: &mut Window,
1413        cx: &mut Context<Workspace>,
1414    ) {
1415        let Some(panel) = workspace.panel::<AssistantPanel>(cx) else {
1416            return;
1417        };
1418
1419        if !panel.focus_handle(cx).contains_focused(window, cx) {
1420            workspace.toggle_panel_focus::<AssistantPanel>(window, cx);
1421        }
1422
1423        let snapshot = buffer.read(cx).snapshot(cx);
1424        let selection_ranges = selection_ranges
1425            .into_iter()
1426            .map(|range| range.to_point(&snapshot))
1427            .collect::<Vec<_>>();
1428
1429        panel.update(cx, |_, cx| {
1430            // Wait to create a new context until the workspace is no longer
1431            // being updated.
1432            cx.defer_in(window, move |panel, window, cx| {
1433                if let Some(context) = panel
1434                    .active_context_editor(cx)
1435                    .or_else(|| panel.new_context(window, cx))
1436                {
1437                    context.update(cx, |context, cx| {
1438                        context.quote_ranges(selection_ranges, snapshot, window, cx)
1439                    });
1440                };
1441            });
1442        });
1443    }
1444}
1445
1446#[derive(Debug, PartialEq, Eq, Clone, Copy)]
1447pub enum WorkflowAssistStatus {
1448    Pending,
1449    Confirmed,
1450    Done,
1451    Idle,
1452}