debugger_panel.rs

   1use crate::persistence::DebuggerPaneItem;
   2use crate::session::DebugSession;
   3use crate::session::running::RunningState;
   4use crate::{
   5    ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
   6    FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, NewProcessModal,
   7    NewProcessMode, Pause, Restart, ShowStackTrace, StepBack, StepInto, StepOut, StepOver, Stop,
   8    ToggleExpandItem, ToggleIgnoreBreakpoints, ToggleSessionPicker, ToggleThreadPicker,
   9    persistence, spawn_task_or_modal,
  10};
  11use anyhow::Result;
  12use command_palette_hooks::CommandPaletteFilter;
  13use dap::adapters::DebugAdapterName;
  14use dap::debugger_settings::DebugPanelDockPosition;
  15use dap::{
  16    ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
  17    client::SessionId, debugger_settings::DebuggerSettings,
  18};
  19use dap::{DapRegistry, StartDebuggingRequestArguments};
  20use gpui::{
  21    Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EntityId, EventEmitter,
  22    FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task, WeakEntity,
  23    actions, anchored, deferred,
  24};
  25
  26use language::Buffer;
  27use project::debugger::session::{Session, SessionStateEvent};
  28use project::{Fs, WorktreeId};
  29use project::{Project, debugger::session::ThreadStatus};
  30use rpc::proto::{self};
  31use settings::Settings;
  32use std::any::TypeId;
  33use std::sync::Arc;
  34use task::{DebugScenario, TaskContext};
  35use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
  36use workspace::SplitDirection;
  37use workspace::{
  38    Pane, Workspace,
  39    dock::{DockPosition, Panel, PanelEvent},
  40};
  41
  42pub enum DebugPanelEvent {
  43    Exited(SessionId),
  44    Terminated(SessionId),
  45    Stopped {
  46        client_id: SessionId,
  47        event: StoppedEvent,
  48        go_to_stack_frame: bool,
  49    },
  50    Thread((SessionId, ThreadEvent)),
  51    Continued((SessionId, ContinuedEvent)),
  52    Output((SessionId, OutputEvent)),
  53    Module((SessionId, ModuleEvent)),
  54    LoadedSource((SessionId, LoadedSourceEvent)),
  55    ClientShutdown(SessionId),
  56    CapabilitiesChanged(SessionId),
  57}
  58
  59actions!(debug_panel, [ToggleFocus]);
  60
  61pub struct DebugPanel {
  62    size: Pixels,
  63    sessions: Vec<Entity<DebugSession>>,
  64    active_session: Option<Entity<DebugSession>>,
  65    project: Entity<Project>,
  66    workspace: WeakEntity<Workspace>,
  67    focus_handle: FocusHandle,
  68    context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
  69    debug_scenario_scheduled_last: bool,
  70    pub(crate) thread_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
  71    pub(crate) session_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
  72    fs: Arc<dyn Fs>,
  73    is_zoomed: bool,
  74    _subscriptions: [Subscription; 1],
  75}
  76
  77impl DebugPanel {
  78    pub fn new(
  79        workspace: &Workspace,
  80        window: &mut Window,
  81        cx: &mut Context<Workspace>,
  82    ) -> Entity<Self> {
  83        cx.new(|cx| {
  84            let project = workspace.project().clone();
  85            let focus_handle = cx.focus_handle();
  86            let thread_picker_menu_handle = PopoverMenuHandle::default();
  87            let session_picker_menu_handle = PopoverMenuHandle::default();
  88
  89            let focus_subscription = cx.on_focus(
  90                &focus_handle,
  91                window,
  92                |this: &mut DebugPanel, window, cx| {
  93                    this.focus_active_item(window, cx);
  94                },
  95            );
  96
  97            Self {
  98                size: px(300.),
  99                sessions: vec![],
 100                active_session: None,
 101                focus_handle,
 102                project,
 103                workspace: workspace.weak_handle(),
 104                context_menu: None,
 105                fs: workspace.app_state().fs.clone(),
 106                thread_picker_menu_handle,
 107                session_picker_menu_handle,
 108                is_zoomed: false,
 109                _subscriptions: [focus_subscription],
 110                debug_scenario_scheduled_last: true,
 111            }
 112        })
 113    }
 114
 115    pub(crate) fn focus_active_item(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 116        let Some(session) = self.active_session.clone() else {
 117            return;
 118        };
 119        let active_pane = session
 120            .read(cx)
 121            .running_state()
 122            .read(cx)
 123            .active_pane()
 124            .clone();
 125        active_pane.update(cx, |pane, cx| {
 126            pane.focus_active_item(window, cx);
 127        });
 128    }
 129
 130    pub(crate) fn sessions(&self) -> Vec<Entity<DebugSession>> {
 131        self.sessions.clone()
 132    }
 133
 134    pub fn active_session(&self) -> Option<Entity<DebugSession>> {
 135        self.active_session.clone()
 136    }
 137
 138    pub(crate) fn running_state(&self, cx: &mut App) -> Option<Entity<RunningState>> {
 139        self.active_session()
 140            .map(|session| session.read(cx).running_state().clone())
 141    }
 142
 143    pub(crate) fn filter_action_types(&self, cx: &mut App) {
 144        let (has_active_session, supports_restart, support_step_back, status) = self
 145            .active_session()
 146            .map(|item| {
 147                let running = item.read(cx).running_state().clone();
 148                let caps = running.read(cx).capabilities(cx);
 149                (
 150                    !running.read(cx).session().read(cx).is_terminated(),
 151                    caps.supports_restart_request.unwrap_or_default(),
 152                    caps.supports_step_back.unwrap_or_default(),
 153                    running.read(cx).thread_status(cx),
 154                )
 155            })
 156            .unwrap_or((false, false, false, None));
 157
 158        let filter = CommandPaletteFilter::global_mut(cx);
 159        let debugger_action_types = [
 160            TypeId::of::<Detach>(),
 161            TypeId::of::<Stop>(),
 162            TypeId::of::<ToggleIgnoreBreakpoints>(),
 163        ];
 164
 165        let running_action_types = [TypeId::of::<Pause>()];
 166
 167        let stopped_action_type = [
 168            TypeId::of::<Continue>(),
 169            TypeId::of::<StepOver>(),
 170            TypeId::of::<StepInto>(),
 171            TypeId::of::<StepOut>(),
 172            TypeId::of::<ShowStackTrace>(),
 173            TypeId::of::<editor::actions::DebuggerRunToCursor>(),
 174            TypeId::of::<editor::actions::DebuggerEvaluateSelectedText>(),
 175        ];
 176
 177        let step_back_action_type = [TypeId::of::<StepBack>()];
 178        let restart_action_type = [TypeId::of::<Restart>()];
 179
 180        if has_active_session {
 181            filter.show_action_types(debugger_action_types.iter());
 182
 183            if supports_restart {
 184                filter.show_action_types(restart_action_type.iter());
 185            } else {
 186                filter.hide_action_types(&restart_action_type);
 187            }
 188
 189            if support_step_back {
 190                filter.show_action_types(step_back_action_type.iter());
 191            } else {
 192                filter.hide_action_types(&step_back_action_type);
 193            }
 194
 195            match status {
 196                Some(ThreadStatus::Running) => {
 197                    filter.show_action_types(running_action_types.iter());
 198                    filter.hide_action_types(&stopped_action_type);
 199                }
 200                Some(ThreadStatus::Stopped) => {
 201                    filter.show_action_types(stopped_action_type.iter());
 202                    filter.hide_action_types(&running_action_types);
 203                }
 204                _ => {
 205                    filter.hide_action_types(&running_action_types);
 206                    filter.hide_action_types(&stopped_action_type);
 207                }
 208            }
 209        } else {
 210            // show only the `debug: start`
 211            filter.hide_action_types(&debugger_action_types);
 212            filter.hide_action_types(&step_back_action_type);
 213            filter.hide_action_types(&restart_action_type);
 214            filter.hide_action_types(&running_action_types);
 215            filter.hide_action_types(&stopped_action_type);
 216        }
 217    }
 218
 219    pub fn load(
 220        workspace: WeakEntity<Workspace>,
 221        cx: &mut AsyncWindowContext,
 222    ) -> Task<Result<Entity<Self>>> {
 223        cx.spawn(async move |cx| {
 224            workspace.update_in(cx, |workspace, window, cx| {
 225                let debug_panel = DebugPanel::new(workspace, window, cx);
 226
 227                workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| {
 228                    workspace.project().read(cx).breakpoint_store().update(
 229                        cx,
 230                        |breakpoint_store, cx| {
 231                            breakpoint_store.clear_breakpoints(cx);
 232                        },
 233                    )
 234                });
 235
 236                cx.observe_new::<DebugPanel>(|debug_panel, _, cx| {
 237                    Self::filter_action_types(debug_panel, cx);
 238                })
 239                .detach();
 240
 241                cx.observe(&debug_panel, |_, debug_panel, cx| {
 242                    debug_panel.update(cx, |debug_panel, cx| {
 243                        Self::filter_action_types(debug_panel, cx);
 244                    });
 245                })
 246                .detach();
 247                workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));
 248
 249                debug_panel
 250            })
 251        })
 252    }
 253
 254    pub fn start_session(
 255        &mut self,
 256        scenario: DebugScenario,
 257        task_context: TaskContext,
 258        active_buffer: Option<Entity<Buffer>>,
 259        worktree_id: Option<WorktreeId>,
 260        window: &mut Window,
 261        cx: &mut Context<Self>,
 262    ) {
 263        let dap_store = self.project.read(cx).dap_store();
 264        let session = dap_store.update(cx, |dap_store, cx| {
 265            dap_store.new_session(
 266                scenario.label.clone(),
 267                DebugAdapterName(scenario.adapter.clone()),
 268                None,
 269                cx,
 270            )
 271        });
 272        self.debug_scenario_scheduled_last = true;
 273        if let Some(inventory) = self
 274            .project
 275            .read(cx)
 276            .task_store()
 277            .read(cx)
 278            .task_inventory()
 279            .cloned()
 280        {
 281            inventory.update(cx, |inventory, _| {
 282                inventory.scenario_scheduled(scenario.clone());
 283            })
 284        }
 285        let task = cx.spawn_in(window, {
 286            let session = session.clone();
 287            async move |this, cx| {
 288                let debug_session =
 289                    Self::register_session(this.clone(), session.clone(), true, cx).await?;
 290                let definition = debug_session
 291                    .update_in(cx, |debug_session, window, cx| {
 292                        debug_session.running_state().update(cx, |running, cx| {
 293                            running.resolve_scenario(
 294                                scenario,
 295                                task_context,
 296                                active_buffer,
 297                                worktree_id,
 298                                window,
 299                                cx,
 300                            )
 301                        })
 302                    })?
 303                    .await?;
 304                dap_store
 305                    .update(cx, |dap_store, cx| {
 306                        dap_store.boot_session(session.clone(), definition, cx)
 307                    })?
 308                    .await
 309            }
 310        });
 311
 312        cx.spawn(async move |_, cx| {
 313            if let Err(error) = task.await {
 314                log::error!("{error}");
 315                session
 316                    .update(cx, |session, cx| {
 317                        session
 318                            .console_output(cx)
 319                            .unbounded_send(format!("error: {}", error))
 320                            .ok();
 321                        session.shutdown(cx)
 322                    })?
 323                    .await;
 324            }
 325            anyhow::Ok(())
 326        })
 327        .detach_and_log_err(cx);
 328    }
 329
 330    pub(crate) fn rerun_last_session(
 331        &mut self,
 332        workspace: &mut Workspace,
 333        window: &mut Window,
 334        cx: &mut Context<Self>,
 335    ) {
 336        let task_store = workspace.project().read(cx).task_store().clone();
 337        let Some(task_inventory) = task_store.read(cx).task_inventory() else {
 338            return;
 339        };
 340        let workspace = self.workspace.clone();
 341        let Some(scenario) = task_inventory.read(cx).last_scheduled_scenario().cloned() else {
 342            window.defer(cx, move |window, cx| {
 343                workspace
 344                    .update(cx, |workspace, cx| {
 345                        NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
 346                    })
 347                    .ok();
 348            });
 349            return;
 350        };
 351
 352        cx.spawn_in(window, async move |this, cx| {
 353            let task_contexts = workspace
 354                .update_in(cx, |workspace, window, cx| {
 355                    tasks_ui::task_contexts(workspace, window, cx)
 356                })?
 357                .await;
 358
 359            let task_context = task_contexts.active_context().cloned().unwrap_or_default();
 360            let worktree_id = task_contexts.worktree();
 361
 362            this.update_in(cx, |this, window, cx| {
 363                this.start_session(
 364                    scenario.clone(),
 365                    task_context,
 366                    None,
 367                    worktree_id,
 368                    window,
 369                    cx,
 370                );
 371            })
 372        })
 373        .detach();
 374    }
 375
 376    pub(crate) async fn register_session(
 377        this: WeakEntity<Self>,
 378        session: Entity<Session>,
 379        focus: bool,
 380        cx: &mut AsyncWindowContext,
 381    ) -> Result<Entity<DebugSession>> {
 382        let debug_session = register_session_inner(&this, session, cx).await?;
 383
 384        let workspace = this.update_in(cx, |this, window, cx| {
 385            if focus {
 386                this.activate_session(debug_session.clone(), window, cx);
 387            }
 388
 389            this.workspace.clone()
 390        })?;
 391        workspace.update_in(cx, |workspace, window, cx| {
 392            workspace.focus_panel::<Self>(window, cx);
 393        })?;
 394        Ok(debug_session)
 395    }
 396
 397    pub(crate) fn handle_restart_request(
 398        &mut self,
 399        mut curr_session: Entity<Session>,
 400        window: &mut Window,
 401        cx: &mut Context<Self>,
 402    ) {
 403        while let Some(parent_session) = curr_session.read(cx).parent_session().cloned() {
 404            curr_session = parent_session;
 405        }
 406
 407        let Some(worktree) = curr_session.read(cx).worktree() else {
 408            log::error!("Attempted to restart a non-running session");
 409            return;
 410        };
 411
 412        let dap_store_handle = self.project.read(cx).dap_store().clone();
 413        let label = curr_session.read(cx).label().clone();
 414        let adapter = curr_session.read(cx).adapter().clone();
 415        let binary = curr_session.read(cx).binary().clone();
 416        let task = curr_session.update(cx, |session, cx| session.shutdown(cx));
 417
 418        cx.spawn_in(window, async move |this, cx| {
 419            task.await;
 420
 421            let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
 422                let session = dap_store.new_session(label, adapter, None, cx);
 423
 424                let task = session.update(cx, |session, cx| {
 425                    session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
 426                });
 427                (session, task)
 428            })?;
 429            Self::register_session(this.clone(), session, true, cx).await?;
 430            task.await
 431        })
 432        .detach_and_log_err(cx);
 433    }
 434
 435    pub fn handle_start_debugging_request(
 436        &mut self,
 437        request: &StartDebuggingRequestArguments,
 438        parent_session: Entity<Session>,
 439        window: &mut Window,
 440        cx: &mut Context<Self>,
 441    ) {
 442        let Some(worktree) = parent_session.read(cx).worktree() else {
 443            log::error!("Attempted to start a child-session from a non-running session");
 444            return;
 445        };
 446
 447        let dap_store_handle = self.project.read(cx).dap_store().clone();
 448        let label = self.label_for_child_session(&parent_session, request, cx);
 449        let adapter = parent_session.read(cx).adapter().clone();
 450        let mut binary = parent_session.read(cx).binary().clone();
 451        binary.request_args = request.clone();
 452        cx.spawn_in(window, async move |this, cx| {
 453            let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
 454                let session =
 455                    dap_store.new_session(label, adapter, Some(parent_session.clone()), cx);
 456
 457                let task = session.update(cx, |session, cx| {
 458                    session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
 459                });
 460                (session, task)
 461            })?;
 462            Self::register_session(this, session, false, cx).await?;
 463            task.await
 464        })
 465        .detach_and_log_err(cx);
 466    }
 467
 468    pub(crate) fn close_session(
 469        &mut self,
 470        entity_id: EntityId,
 471        window: &mut Window,
 472        cx: &mut Context<Self>,
 473    ) {
 474        let Some(session) = self
 475            .sessions
 476            .iter()
 477            .find(|other| entity_id == other.entity_id())
 478            .cloned()
 479        else {
 480            return;
 481        };
 482        session.update(cx, |this, cx| {
 483            this.running_state().update(cx, |this, cx| {
 484                this.serialize_layout(window, cx);
 485            });
 486        });
 487        let session_id = session.update(cx, |this, cx| this.session_id(cx));
 488        let should_prompt = self
 489            .project
 490            .update(cx, |this, cx| {
 491                let session = this.dap_store().read(cx).session_by_id(session_id);
 492                session.map(|session| !session.read(cx).is_terminated())
 493            })
 494            .unwrap_or_default();
 495
 496        cx.spawn_in(window, async move |this, cx| {
 497            if should_prompt {
 498                let response = cx.prompt(
 499                    gpui::PromptLevel::Warning,
 500                    "This Debug Session is still running. Are you sure you want to terminate it?",
 501                    None,
 502                    &["Yes", "No"],
 503                );
 504                if response.await == Ok(1) {
 505                    return;
 506                }
 507            }
 508            session.update(cx, |session, cx| session.shutdown(cx)).ok();
 509            this.update(cx, |this, cx| {
 510                this.sessions.retain(|other| entity_id != other.entity_id());
 511
 512                if let Some(active_session_id) = this
 513                    .active_session
 514                    .as_ref()
 515                    .map(|session| session.entity_id())
 516                {
 517                    if active_session_id == entity_id {
 518                        this.active_session = this.sessions.first().cloned();
 519                    }
 520                }
 521                cx.notify()
 522            })
 523            .ok();
 524        })
 525        .detach();
 526    }
 527
 528    pub(crate) fn deploy_context_menu(
 529        &mut self,
 530        position: Point<Pixels>,
 531        window: &mut Window,
 532        cx: &mut Context<Self>,
 533    ) {
 534        if let Some(running_state) = self
 535            .active_session
 536            .as_ref()
 537            .map(|session| session.read(cx).running_state().clone())
 538        {
 539            let pane_items_status = running_state.read(cx).pane_items_status(cx);
 540            let this = cx.weak_entity();
 541
 542            let context_menu = ContextMenu::build(window, cx, |mut menu, _window, _cx| {
 543                for (item_kind, is_visible) in pane_items_status.into_iter() {
 544                    menu = menu.toggleable_entry(item_kind, is_visible, IconPosition::End, None, {
 545                        let this = this.clone();
 546                        move |window, cx| {
 547                            this.update(cx, |this, cx| {
 548                                if let Some(running_state) = this
 549                                    .active_session
 550                                    .as_ref()
 551                                    .map(|session| session.read(cx).running_state().clone())
 552                                {
 553                                    running_state.update(cx, |state, cx| {
 554                                        if is_visible {
 555                                            state.remove_pane_item(item_kind, window, cx);
 556                                        } else {
 557                                            state.add_pane_item(item_kind, position, window, cx);
 558                                        }
 559                                    })
 560                                }
 561                            })
 562                            .ok();
 563                        }
 564                    });
 565                }
 566
 567                menu
 568            });
 569
 570            window.focus(&context_menu.focus_handle(cx));
 571            let subscription = cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
 572                this.context_menu.take();
 573                cx.notify();
 574            });
 575            self.context_menu = Some((context_menu, position, subscription));
 576        }
 577    }
 578
 579    pub(crate) fn top_controls_strip(
 580        &mut self,
 581        window: &mut Window,
 582        cx: &mut Context<Self>,
 583    ) -> Option<Div> {
 584        let active_session = self.active_session.clone();
 585        let focus_handle = self.focus_handle.clone();
 586        let is_side = self.position(window, cx).axis() == gpui::Axis::Horizontal;
 587        let div = if is_side { v_flex() } else { h_flex() };
 588
 589        let new_session_button = || {
 590            IconButton::new("debug-new-session", IconName::Plus)
 591                .icon_size(IconSize::Small)
 592                .on_click({
 593                    move |_, window, cx| window.dispatch_action(crate::Start.boxed_clone(), cx)
 594                })
 595                .tooltip({
 596                    let focus_handle = focus_handle.clone();
 597                    move |window, cx| {
 598                        Tooltip::for_action_in(
 599                            "Start Debug Session",
 600                            &crate::Start,
 601                            &focus_handle,
 602                            window,
 603                            cx,
 604                        )
 605                    }
 606                })
 607        };
 608        let documentation_button = || {
 609            IconButton::new("debug-open-documentation", IconName::CircleHelp)
 610                .icon_size(IconSize::Small)
 611                .on_click(move |_, _, cx| cx.open_url("https://zed.dev/docs/debugger"))
 612                .tooltip(Tooltip::text("Open Documentation"))
 613        };
 614
 615        Some(
 616            div.border_b_1()
 617                .border_color(cx.theme().colors().border)
 618                .p_1()
 619                .justify_between()
 620                .w_full()
 621                .when(is_side, |this| this.gap_1())
 622                .child(
 623                    h_flex()
 624                        .child(
 625                            h_flex().gap_2().w_full().when_some(
 626                                active_session
 627                                    .as_ref()
 628                                    .map(|session| session.read(cx).running_state()),
 629                                |this, running_state| {
 630                                    let thread_status =
 631                                        running_state.read(cx).thread_status(cx).unwrap_or(
 632                                            project::debugger::session::ThreadStatus::Exited,
 633                                        );
 634                                    let capabilities = running_state.read(cx).capabilities(cx);
 635                                    this.map(|this| {
 636                                        if thread_status == ThreadStatus::Running {
 637                                            this.child(
 638                                                IconButton::new(
 639                                                    "debug-pause",
 640                                                    IconName::DebugPause,
 641                                                )
 642                                                .icon_size(IconSize::XSmall)
 643                                                .shape(ui::IconButtonShape::Square)
 644                                                .on_click(window.listener_for(
 645                                                    &running_state,
 646                                                    |this, _, _window, cx| {
 647                                                        this.pause_thread(cx);
 648                                                    },
 649                                                ))
 650                                                .tooltip({
 651                                                    let focus_handle = focus_handle.clone();
 652                                                    move |window, cx| {
 653                                                        Tooltip::for_action_in(
 654                                                            "Pause program",
 655                                                            &Pause,
 656                                                            &focus_handle,
 657                                                            window,
 658                                                            cx,
 659                                                        )
 660                                                    }
 661                                                }),
 662                                            )
 663                                        } else {
 664                                            this.child(
 665                                                IconButton::new(
 666                                                    "debug-continue",
 667                                                    IconName::DebugContinue,
 668                                                )
 669                                                .icon_size(IconSize::XSmall)
 670                                                .shape(ui::IconButtonShape::Square)
 671                                                .on_click(window.listener_for(
 672                                                    &running_state,
 673                                                    |this, _, _window, cx| this.continue_thread(cx),
 674                                                ))
 675                                                .disabled(thread_status != ThreadStatus::Stopped)
 676                                                .tooltip({
 677                                                    let focus_handle = focus_handle.clone();
 678                                                    move |window, cx| {
 679                                                        Tooltip::for_action_in(
 680                                                            "Continue program",
 681                                                            &Continue,
 682                                                            &focus_handle,
 683                                                            window,
 684                                                            cx,
 685                                                        )
 686                                                    }
 687                                                }),
 688                                            )
 689                                        }
 690                                    })
 691                                    .child(
 692                                        IconButton::new("debug-step-over", IconName::ArrowRight)
 693                                            .icon_size(IconSize::XSmall)
 694                                            .shape(ui::IconButtonShape::Square)
 695                                            .on_click(window.listener_for(
 696                                                &running_state,
 697                                                |this, _, _window, cx| {
 698                                                    this.step_over(cx);
 699                                                },
 700                                            ))
 701                                            .disabled(thread_status != ThreadStatus::Stopped)
 702                                            .tooltip({
 703                                                let focus_handle = focus_handle.clone();
 704                                                move |window, cx| {
 705                                                    Tooltip::for_action_in(
 706                                                        "Step over",
 707                                                        &StepOver,
 708                                                        &focus_handle,
 709                                                        window,
 710                                                        cx,
 711                                                    )
 712                                                }
 713                                            }),
 714                                    )
 715                                    .child(
 716                                        IconButton::new("debug-step-out", IconName::ArrowUpRight)
 717                                            .icon_size(IconSize::XSmall)
 718                                            .shape(ui::IconButtonShape::Square)
 719                                            .on_click(window.listener_for(
 720                                                &running_state,
 721                                                |this, _, _window, cx| {
 722                                                    this.step_out(cx);
 723                                                },
 724                                            ))
 725                                            .disabled(thread_status != ThreadStatus::Stopped)
 726                                            .tooltip({
 727                                                let focus_handle = focus_handle.clone();
 728                                                move |window, cx| {
 729                                                    Tooltip::for_action_in(
 730                                                        "Step out",
 731                                                        &StepOut,
 732                                                        &focus_handle,
 733                                                        window,
 734                                                        cx,
 735                                                    )
 736                                                }
 737                                            }),
 738                                    )
 739                                    .child(
 740                                        IconButton::new(
 741                                            "debug-step-into",
 742                                            IconName::ArrowDownRight,
 743                                        )
 744                                        .icon_size(IconSize::XSmall)
 745                                        .shape(ui::IconButtonShape::Square)
 746                                        .on_click(window.listener_for(
 747                                            &running_state,
 748                                            |this, _, _window, cx| {
 749                                                this.step_in(cx);
 750                                            },
 751                                        ))
 752                                        .disabled(thread_status != ThreadStatus::Stopped)
 753                                        .tooltip({
 754                                            let focus_handle = focus_handle.clone();
 755                                            move |window, cx| {
 756                                                Tooltip::for_action_in(
 757                                                    "Step in",
 758                                                    &StepInto,
 759                                                    &focus_handle,
 760                                                    window,
 761                                                    cx,
 762                                                )
 763                                            }
 764                                        }),
 765                                    )
 766                                    .child(Divider::vertical())
 767                                    .child(
 768                                        IconButton::new("debug-restart", IconName::DebugRestart)
 769                                            .icon_size(IconSize::XSmall)
 770                                            .on_click(window.listener_for(
 771                                                &running_state,
 772                                                |this, _, _window, cx| {
 773                                                    this.restart_session(cx);
 774                                                },
 775                                            ))
 776                                            .tooltip({
 777                                                let focus_handle = focus_handle.clone();
 778                                                move |window, cx| {
 779                                                    Tooltip::for_action_in(
 780                                                        "Restart",
 781                                                        &Restart,
 782                                                        &focus_handle,
 783                                                        window,
 784                                                        cx,
 785                                                    )
 786                                                }
 787                                            }),
 788                                    )
 789                                    .child(
 790                                        IconButton::new("debug-stop", IconName::Power)
 791                                            .icon_size(IconSize::XSmall)
 792                                            .on_click(window.listener_for(
 793                                                &running_state,
 794                                                |this, _, _window, cx| {
 795                                                    this.stop_thread(cx);
 796                                                },
 797                                            ))
 798                                            .disabled(
 799                                                thread_status != ThreadStatus::Stopped
 800                                                    && thread_status != ThreadStatus::Running,
 801                                            )
 802                                            .tooltip({
 803                                                let focus_handle = focus_handle.clone();
 804                                                let label = if capabilities
 805                                                    .supports_terminate_threads_request
 806                                                    .unwrap_or_default()
 807                                                {
 808                                                    "Terminate Thread"
 809                                                } else {
 810                                                    "Terminate All Threads"
 811                                                };
 812                                                move |window, cx| {
 813                                                    Tooltip::for_action_in(
 814                                                        label,
 815                                                        &Stop,
 816                                                        &focus_handle,
 817                                                        window,
 818                                                        cx,
 819                                                    )
 820                                                }
 821                                            }),
 822                                    )
 823                                    .child(
 824                                        IconButton::new("debug-disconnect", IconName::DebugDetach)
 825                                            .icon_size(IconSize::XSmall)
 826                                            .on_click(window.listener_for(
 827                                                &running_state,
 828                                                |this, _, _, cx| {
 829                                                    this.detach_client(cx);
 830                                                },
 831                                            ))
 832                                            .tooltip({
 833                                                let focus_handle = focus_handle.clone();
 834                                                move |window, cx| {
 835                                                    Tooltip::for_action_in(
 836                                                        "Detach",
 837                                                        &Detach,
 838                                                        &focus_handle,
 839                                                        window,
 840                                                        cx,
 841                                                    )
 842                                                }
 843                                            }),
 844                                    )
 845                                },
 846                            ),
 847                        )
 848                        .justify_around()
 849                        .when(is_side, |this| {
 850                            this.child(new_session_button())
 851                                .child(documentation_button())
 852                        }),
 853                )
 854                .child(
 855                    h_flex()
 856                        .gap_2()
 857                        .when(is_side, |this| this.justify_between())
 858                        .child(
 859                            h_flex().when_some(
 860                                active_session
 861                                    .as_ref()
 862                                    .map(|session| session.read(cx).running_state())
 863                                    .cloned(),
 864                                |this, running_state| {
 865                                    this.children({
 866                                        let running_state = running_state.clone();
 867                                        let threads =
 868                                            running_state.update(cx, |running_state, cx| {
 869                                                let session = running_state.session();
 870                                                session
 871                                                    .update(cx, |session, cx| session.threads(cx))
 872                                            });
 873
 874                                        self.render_thread_dropdown(
 875                                            &running_state,
 876                                            threads,
 877                                            window,
 878                                            cx,
 879                                        )
 880                                    })
 881                                    .when(!is_side, |this| this.gap_2().child(Divider::vertical()))
 882                                },
 883                            ),
 884                        )
 885                        .child(
 886                            h_flex()
 887                                .children(self.render_session_menu(
 888                                    self.active_session(),
 889                                    self.running_state(cx),
 890                                    window,
 891                                    cx,
 892                                ))
 893                                .when(!is_side, |this| {
 894                                    this.child(new_session_button())
 895                                        .child(documentation_button())
 896                                }),
 897                        ),
 898                ),
 899        )
 900    }
 901
 902    pub(crate) fn activate_pane_in_direction(
 903        &mut self,
 904        direction: SplitDirection,
 905        window: &mut Window,
 906        cx: &mut Context<Self>,
 907    ) {
 908        if let Some(session) = self.active_session() {
 909            session.update(cx, |session, cx| {
 910                session.running_state().update(cx, |running, cx| {
 911                    running.activate_pane_in_direction(direction, window, cx);
 912                })
 913            });
 914        }
 915    }
 916
 917    pub(crate) fn activate_item(
 918        &mut self,
 919        item: DebuggerPaneItem,
 920        window: &mut Window,
 921        cx: &mut Context<Self>,
 922    ) {
 923        if let Some(session) = self.active_session() {
 924            session.update(cx, |session, cx| {
 925                session.running_state().update(cx, |running, cx| {
 926                    running.activate_item(item, window, cx);
 927                });
 928            });
 929        }
 930    }
 931
 932    pub(crate) fn activate_session_by_id(
 933        &mut self,
 934        session_id: SessionId,
 935        window: &mut Window,
 936        cx: &mut Context<Self>,
 937    ) {
 938        if let Some(session) = self
 939            .sessions
 940            .iter()
 941            .find(|session| session.read(cx).session_id(cx) == session_id)
 942        {
 943            self.activate_session(session.clone(), window, cx);
 944        }
 945    }
 946
 947    pub(crate) fn activate_session(
 948        &mut self,
 949        session_item: Entity<DebugSession>,
 950        window: &mut Window,
 951        cx: &mut Context<Self>,
 952    ) {
 953        debug_assert!(self.sessions.contains(&session_item));
 954        session_item.focus_handle(cx).focus(window);
 955        session_item.update(cx, |this, cx| {
 956            this.running_state().update(cx, |this, cx| {
 957                this.go_to_selected_stack_frame(window, cx);
 958            });
 959        });
 960        self.active_session = Some(session_item);
 961        cx.notify();
 962    }
 963
 964    // TODO: restore once we have proper comment preserving file edits
 965    // pub(crate) fn save_scenario(
 966    //     &self,
 967    //     scenario: &DebugScenario,
 968    //     worktree_id: WorktreeId,
 969    //     window: &mut Window,
 970    //     cx: &mut App,
 971    // ) -> Task<Result<ProjectPath>> {
 972    //     self.workspace
 973    //         .update(cx, |workspace, cx| {
 974    //             let Some(mut path) = workspace.absolute_path_of_worktree(worktree_id, cx) else {
 975    //                 return Task::ready(Err(anyhow!("Couldn't get worktree path")));
 976    //             };
 977
 978    //             let serialized_scenario = serde_json::to_value(scenario);
 979
 980    //             cx.spawn_in(window, async move |workspace, cx| {
 981    //                 let serialized_scenario = serialized_scenario?;
 982    //                 let fs =
 983    //                     workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
 984
 985    //                 path.push(paths::local_settings_folder_relative_path());
 986    //                 if !fs.is_dir(path.as_path()).await {
 987    //                     fs.create_dir(path.as_path()).await?;
 988    //                 }
 989    //                 path.pop();
 990
 991    //                 path.push(paths::local_debug_file_relative_path());
 992    //                 let path = path.as_path();
 993
 994    //                 if !fs.is_file(path).await {
 995    //                     fs.create_file(path, Default::default()).await?;
 996    //                     fs.write(
 997    //                         path,
 998    //                         initial_local_debug_tasks_content().to_string().as_bytes(),
 999    //                     )
1000    //                     .await?;
1001    //                 }
1002
1003    //                 let content = fs.load(path).await?;
1004    //                 let mut values =
1005    //                     serde_json_lenient::from_str::<Vec<serde_json::Value>>(&content)?;
1006    //                 values.push(serialized_scenario);
1007    //                 fs.save(
1008    //                     path,
1009    //                     &serde_json_lenient::to_string_pretty(&values).map(Into::into)?,
1010    //                     Default::default(),
1011    //                 )
1012    //                 .await?;
1013
1014    //                 workspace.update(cx, |workspace, cx| {
1015    //                     workspace
1016    //                         .project()
1017    //                         .read(cx)
1018    //                         .project_path_for_absolute_path(&path, cx)
1019    //                         .context(
1020    //                             "Couldn't get project path for .zed/debug.json in active worktree",
1021    //                         )
1022    //                 })?
1023    //             })
1024    //         })
1025    //         .unwrap_or_else(|err| Task::ready(Err(err)))
1026    // }
1027
1028    pub(crate) fn toggle_thread_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1029        self.thread_picker_menu_handle.toggle(window, cx);
1030    }
1031
1032    pub(crate) fn toggle_session_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1033        self.session_picker_menu_handle.toggle(window, cx);
1034    }
1035
1036    fn toggle_zoom(
1037        &mut self,
1038        _: &workspace::ToggleZoom,
1039        window: &mut Window,
1040        cx: &mut Context<Self>,
1041    ) {
1042        if self.is_zoomed {
1043            cx.emit(PanelEvent::ZoomOut);
1044        } else {
1045            if !self.focus_handle(cx).contains_focused(window, cx) {
1046                cx.focus_self(window);
1047            }
1048            cx.emit(PanelEvent::ZoomIn);
1049        }
1050    }
1051
1052    fn label_for_child_session(
1053        &self,
1054        parent_session: &Entity<Session>,
1055        request: &StartDebuggingRequestArguments,
1056        cx: &mut Context<'_, Self>,
1057    ) -> SharedString {
1058        let adapter = parent_session.read(cx).adapter();
1059        if let Some(adapter) = DapRegistry::global(cx).adapter(&adapter) {
1060            if let Some(label) = adapter.label_for_child_session(request) {
1061                return label.into();
1062            }
1063        }
1064        let mut label = parent_session.read(cx).label().clone();
1065        if !label.ends_with("(child)") {
1066            label = format!("{label} (child)").into();
1067        }
1068        label
1069    }
1070}
1071
1072async fn register_session_inner(
1073    this: &WeakEntity<DebugPanel>,
1074    session: Entity<Session>,
1075    cx: &mut AsyncWindowContext,
1076) -> Result<Entity<DebugSession>> {
1077    let adapter_name = session.read_with(cx, |session, _| session.adapter())?;
1078    this.update_in(cx, |_, window, cx| {
1079        cx.subscribe_in(
1080            &session,
1081            window,
1082            move |this, session, event: &SessionStateEvent, window, cx| match event {
1083                SessionStateEvent::Restart => {
1084                    this.handle_restart_request(session.clone(), window, cx);
1085                }
1086                SessionStateEvent::SpawnChildSession { request } => {
1087                    this.handle_start_debugging_request(request, session.clone(), window, cx);
1088                }
1089                _ => {}
1090            },
1091        )
1092        .detach();
1093    })
1094    .ok();
1095    let serialized_layout = persistence::get_serialized_layout(adapter_name).await;
1096    let debug_session = this.update_in(cx, |this, window, cx| {
1097        let parent_session = this
1098            .sessions
1099            .iter()
1100            .find(|p| Some(p.read(cx).session_id(cx)) == session.read(cx).parent_id(cx))
1101            .cloned();
1102        this.sessions.retain(|session| {
1103            !session
1104                .read(cx)
1105                .running_state()
1106                .read(cx)
1107                .session()
1108                .read(cx)
1109                .is_terminated()
1110        });
1111
1112        let debug_session = DebugSession::running(
1113            this.project.clone(),
1114            this.workspace.clone(),
1115            parent_session.map(|p| p.read(cx).running_state().read(cx).debug_terminal.clone()),
1116            session,
1117            serialized_layout,
1118            this.position(window, cx).axis(),
1119            window,
1120            cx,
1121        );
1122
1123        // We might want to make this an event subscription and only notify when a new thread is selected
1124        // This is used to filter the command menu correctly
1125        cx.observe(
1126            &debug_session.read(cx).running_state().clone(),
1127            |_, _, cx| cx.notify(),
1128        )
1129        .detach();
1130
1131        this.sessions.push(debug_session.clone());
1132
1133        debug_session
1134    })?;
1135    Ok(debug_session)
1136}
1137
1138impl EventEmitter<PanelEvent> for DebugPanel {}
1139impl EventEmitter<DebugPanelEvent> for DebugPanel {}
1140
1141impl Focusable for DebugPanel {
1142    fn focus_handle(&self, _: &App) -> FocusHandle {
1143        self.focus_handle.clone()
1144    }
1145}
1146
1147impl Panel for DebugPanel {
1148    fn persistent_name() -> &'static str {
1149        "DebugPanel"
1150    }
1151
1152    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1153        match DebuggerSettings::get_global(cx).dock {
1154            DebugPanelDockPosition::Left => DockPosition::Left,
1155            DebugPanelDockPosition::Bottom => DockPosition::Bottom,
1156            DebugPanelDockPosition::Right => DockPosition::Right,
1157        }
1158    }
1159
1160    fn position_is_valid(&self, _: DockPosition) -> bool {
1161        true
1162    }
1163
1164    fn set_position(
1165        &mut self,
1166        position: DockPosition,
1167        window: &mut Window,
1168        cx: &mut Context<Self>,
1169    ) {
1170        if position.axis() != self.position(window, cx).axis() {
1171            self.sessions.iter().for_each(|session_item| {
1172                session_item.update(cx, |item, cx| {
1173                    item.running_state()
1174                        .update(cx, |state, _| state.invert_axies())
1175                })
1176            });
1177        }
1178
1179        settings::update_settings_file::<DebuggerSettings>(
1180            self.fs.clone(),
1181            cx,
1182            move |settings, _| {
1183                let dock = match position {
1184                    DockPosition::Left => DebugPanelDockPosition::Left,
1185                    DockPosition::Bottom => DebugPanelDockPosition::Bottom,
1186                    DockPosition::Right => DebugPanelDockPosition::Right,
1187                };
1188                settings.dock = dock;
1189            },
1190        );
1191    }
1192
1193    fn size(&self, _window: &Window, _: &App) -> Pixels {
1194        self.size
1195    }
1196
1197    fn set_size(&mut self, size: Option<Pixels>, _window: &mut Window, _cx: &mut Context<Self>) {
1198        self.size = size.unwrap_or(px(300.));
1199    }
1200
1201    fn remote_id() -> Option<proto::PanelId> {
1202        Some(proto::PanelId::DebugPanel)
1203    }
1204
1205    fn icon(&self, _window: &Window, _cx: &App) -> Option<IconName> {
1206        Some(IconName::Debug)
1207    }
1208
1209    fn icon_tooltip(&self, _window: &Window, cx: &App) -> Option<&'static str> {
1210        if DebuggerSettings::get_global(cx).button {
1211            Some("Debug Panel")
1212        } else {
1213            None
1214        }
1215    }
1216
1217    fn toggle_action(&self) -> Box<dyn Action> {
1218        Box::new(ToggleFocus)
1219    }
1220
1221    fn pane(&self) -> Option<Entity<Pane>> {
1222        None
1223    }
1224
1225    fn activation_priority(&self) -> u32 {
1226        9
1227    }
1228
1229    fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
1230
1231    fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
1232        self.is_zoomed
1233    }
1234
1235    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, cx: &mut Context<Self>) {
1236        self.is_zoomed = zoomed;
1237        cx.notify();
1238    }
1239}
1240
1241impl Render for DebugPanel {
1242    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1243        let has_sessions = self.sessions.len() > 0;
1244        let this = cx.weak_entity();
1245        debug_assert_eq!(has_sessions, self.active_session.is_some());
1246
1247        if self
1248            .active_session
1249            .as_ref()
1250            .map(|session| session.read(cx).running_state())
1251            .map(|state| state.read(cx).has_open_context_menu(cx))
1252            .unwrap_or(false)
1253        {
1254            self.context_menu.take();
1255        }
1256
1257        v_flex()
1258            .size_full()
1259            .key_context("DebugPanel")
1260            .child(h_flex().children(self.top_controls_strip(window, cx)))
1261            .track_focus(&self.focus_handle(cx))
1262            .on_action({
1263                let this = this.clone();
1264                move |_: &workspace::ActivatePaneLeft, window, cx| {
1265                    this.update(cx, |this, cx| {
1266                        this.activate_pane_in_direction(SplitDirection::Left, window, cx);
1267                    })
1268                    .ok();
1269                }
1270            })
1271            .on_action({
1272                let this = this.clone();
1273                move |_: &workspace::ActivatePaneRight, window, cx| {
1274                    this.update(cx, |this, cx| {
1275                        this.activate_pane_in_direction(SplitDirection::Right, window, cx);
1276                    })
1277                    .ok();
1278                }
1279            })
1280            .on_action({
1281                let this = this.clone();
1282                move |_: &workspace::ActivatePaneUp, window, cx| {
1283                    this.update(cx, |this, cx| {
1284                        this.activate_pane_in_direction(SplitDirection::Up, window, cx);
1285                    })
1286                    .ok();
1287                }
1288            })
1289            .on_action({
1290                let this = this.clone();
1291                move |_: &workspace::ActivatePaneDown, window, cx| {
1292                    this.update(cx, |this, cx| {
1293                        this.activate_pane_in_direction(SplitDirection::Down, window, cx);
1294                    })
1295                    .ok();
1296                }
1297            })
1298            .on_action({
1299                let this = this.clone();
1300                move |_: &FocusConsole, window, cx| {
1301                    this.update(cx, |this, cx| {
1302                        this.activate_item(DebuggerPaneItem::Console, window, cx);
1303                    })
1304                    .ok();
1305                }
1306            })
1307            .on_action({
1308                let this = this.clone();
1309                move |_: &FocusVariables, window, cx| {
1310                    this.update(cx, |this, cx| {
1311                        this.activate_item(DebuggerPaneItem::Variables, window, cx);
1312                    })
1313                    .ok();
1314                }
1315            })
1316            .on_action({
1317                let this = this.clone();
1318                move |_: &FocusBreakpointList, window, cx| {
1319                    this.update(cx, |this, cx| {
1320                        this.activate_item(DebuggerPaneItem::BreakpointList, window, cx);
1321                    })
1322                    .ok();
1323                }
1324            })
1325            .on_action({
1326                let this = this.clone();
1327                move |_: &FocusFrames, window, cx| {
1328                    this.update(cx, |this, cx| {
1329                        this.activate_item(DebuggerPaneItem::Frames, window, cx);
1330                    })
1331                    .ok();
1332                }
1333            })
1334            .on_action({
1335                let this = this.clone();
1336                move |_: &FocusModules, window, cx| {
1337                    this.update(cx, |this, cx| {
1338                        this.activate_item(DebuggerPaneItem::Modules, window, cx);
1339                    })
1340                    .ok();
1341                }
1342            })
1343            .on_action({
1344                let this = this.clone();
1345                move |_: &FocusLoadedSources, window, cx| {
1346                    this.update(cx, |this, cx| {
1347                        this.activate_item(DebuggerPaneItem::LoadedSources, window, cx);
1348                    })
1349                    .ok();
1350                }
1351            })
1352            .on_action({
1353                let this = this.clone();
1354                move |_: &FocusTerminal, window, cx| {
1355                    this.update(cx, |this, cx| {
1356                        this.activate_item(DebuggerPaneItem::Terminal, window, cx);
1357                    })
1358                    .ok();
1359                }
1360            })
1361            .on_action({
1362                let this = this.clone();
1363                move |_: &ToggleThreadPicker, window, cx| {
1364                    this.update(cx, |this, cx| {
1365                        this.toggle_thread_picker(window, cx);
1366                    })
1367                    .ok();
1368                }
1369            })
1370            .on_action({
1371                let this = this.clone();
1372                move |_: &ToggleSessionPicker, window, cx| {
1373                    this.update(cx, |this, cx| {
1374                        this.toggle_session_picker(window, cx);
1375                    })
1376                    .ok();
1377                }
1378            })
1379            .on_action(cx.listener(Self::toggle_zoom))
1380            .on_action(cx.listener(|panel, _: &ToggleExpandItem, _, cx| {
1381                let Some(session) = panel.active_session() else {
1382                    return;
1383                };
1384                let active_pane = session
1385                    .read(cx)
1386                    .running_state()
1387                    .read(cx)
1388                    .active_pane()
1389                    .clone();
1390                active_pane.update(cx, |pane, cx| {
1391                    let is_zoomed = pane.is_zoomed();
1392                    pane.set_zoomed(!is_zoomed, cx);
1393                });
1394                cx.notify();
1395            }))
1396            .when(self.active_session.is_some(), |this| {
1397                this.on_mouse_down(
1398                    MouseButton::Right,
1399                    cx.listener(|this, event: &MouseDownEvent, window, cx| {
1400                        if this
1401                            .active_session
1402                            .as_ref()
1403                            .map(|session| {
1404                                let state = session.read(cx).running_state();
1405                                state.read(cx).has_pane_at_position(event.position)
1406                            })
1407                            .unwrap_or(false)
1408                        {
1409                            this.deploy_context_menu(event.position, window, cx);
1410                        }
1411                    }),
1412                )
1413                .children(self.context_menu.as_ref().map(|(menu, position, _)| {
1414                    deferred(
1415                        anchored()
1416                            .position(*position)
1417                            .anchor(gpui::Corner::TopLeft)
1418                            .child(menu.clone()),
1419                    )
1420                    .with_priority(1)
1421                }))
1422            })
1423            .map(|this| {
1424                if has_sessions {
1425                    this.children(self.active_session.clone())
1426                } else {
1427                    this.child(
1428                        v_flex()
1429                            .h_full()
1430                            .gap_1()
1431                            .items_center()
1432                            .justify_center()
1433                            .child(
1434                                h_flex().child(
1435                                    Label::new("No Debugging Sessions")
1436                                        .size(LabelSize::Small)
1437                                        .color(Color::Muted),
1438                                ),
1439                            )
1440                            .child(
1441                                h_flex().flex_shrink().child(
1442                                    Button::new("spawn-new-session-empty-state", "New Session")
1443                                        .size(ButtonSize::Large)
1444                                        .on_click(|_, window, cx| {
1445                                            window.dispatch_action(crate::Start.boxed_clone(), cx);
1446                                        }),
1447                                ),
1448                            ),
1449                    )
1450                }
1451            })
1452            .into_any()
1453    }
1454}
1455
1456struct DebuggerProvider(Entity<DebugPanel>);
1457
1458impl workspace::DebuggerProvider for DebuggerProvider {
1459    fn start_session(
1460        &self,
1461        definition: DebugScenario,
1462        context: TaskContext,
1463        buffer: Option<Entity<Buffer>>,
1464        window: &mut Window,
1465        cx: &mut App,
1466    ) {
1467        self.0.update(cx, |_, cx| {
1468            cx.defer_in(window, |this, window, cx| {
1469                this.start_session(definition, context, buffer, None, window, cx);
1470            })
1471        })
1472    }
1473
1474    fn spawn_task_or_modal(
1475        &self,
1476        workspace: &mut Workspace,
1477        action: &tasks_ui::Spawn,
1478        window: &mut Window,
1479        cx: &mut Context<Workspace>,
1480    ) {
1481        spawn_task_or_modal(workspace, action, window, cx);
1482    }
1483
1484    fn debug_scenario_scheduled(&self, cx: &mut App) {
1485        self.0.update(cx, |this, _| {
1486            this.debug_scenario_scheduled_last = true;
1487        });
1488    }
1489
1490    fn task_scheduled(&self, cx: &mut App) {
1491        self.0.update(cx, |this, _| {
1492            this.debug_scenario_scheduled_last = false;
1493        })
1494    }
1495
1496    fn debug_scenario_scheduled_last(&self, cx: &App) -> bool {
1497        self.0.read(cx).debug_scenario_scheduled_last
1498    }
1499
1500    fn active_thread_state(&self, cx: &App) -> Option<ThreadStatus> {
1501        let session = self.0.read(cx).active_session()?;
1502        let thread = session.read(cx).running_state().read(cx).thread_id()?;
1503        session.read(cx).session(cx).read(cx).thread_state(thread)
1504    }
1505}