debugger_panel.rs

  1use crate::{
  2    ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, Pause, Restart, StepBack,
  3    StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
  4};
  5use crate::{new_session_modal::NewSessionModal, session::DebugSession};
  6use anyhow::{Result, anyhow};
  7use collections::HashMap;
  8use command_palette_hooks::CommandPaletteFilter;
  9use dap::{
 10    ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
 11    client::SessionId, debugger_settings::DebuggerSettings,
 12};
 13use futures::{SinkExt as _, channel::mpsc};
 14use gpui::{
 15    Action, App, AsyncWindowContext, Context, Entity, EventEmitter, FocusHandle, Focusable,
 16    Subscription, Task, WeakEntity, actions,
 17};
 18use project::{
 19    Project,
 20    debugger::{
 21        dap_store::{self, DapStore},
 22        session::ThreadStatus,
 23    },
 24    terminals::TerminalKind,
 25};
 26use rpc::proto::{self};
 27use settings::Settings;
 28use std::{any::TypeId, path::PathBuf};
 29use task::DebugTaskDefinition;
 30use terminal_view::terminal_panel::TerminalPanel;
 31use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
 32use workspace::{
 33    Pane, Workspace,
 34    dock::{DockPosition, Panel, PanelEvent},
 35    pane,
 36};
 37
 38pub enum DebugPanelEvent {
 39    Exited(SessionId),
 40    Terminated(SessionId),
 41    Stopped {
 42        client_id: SessionId,
 43        event: StoppedEvent,
 44        go_to_stack_frame: bool,
 45    },
 46    Thread((SessionId, ThreadEvent)),
 47    Continued((SessionId, ContinuedEvent)),
 48    Output((SessionId, OutputEvent)),
 49    Module((SessionId, ModuleEvent)),
 50    LoadedSource((SessionId, LoadedSourceEvent)),
 51    ClientShutdown(SessionId),
 52    CapabilitiesChanged(SessionId),
 53}
 54
 55actions!(debug_panel, [ToggleFocus]);
 56pub struct DebugPanel {
 57    size: Pixels,
 58    pane: Entity<Pane>,
 59    /// This represents the last debug definition that was created in the new session modal
 60    pub(crate) past_debug_definition: Option<DebugTaskDefinition>,
 61    project: WeakEntity<Project>,
 62    workspace: WeakEntity<Workspace>,
 63    _subscriptions: Vec<Subscription>,
 64}
 65
 66impl DebugPanel {
 67    pub fn new(
 68        workspace: &Workspace,
 69        window: &mut Window,
 70        cx: &mut Context<Workspace>,
 71    ) -> Entity<Self> {
 72        cx.new(|cx| {
 73            let project = workspace.project().clone();
 74            let dap_store = project.read(cx).dap_store();
 75            let pane = cx.new(|cx| {
 76                let mut pane = Pane::new(
 77                    workspace.weak_handle(),
 78                    project.clone(),
 79                    Default::default(),
 80                    None,
 81                    gpui::NoAction.boxed_clone(),
 82                    window,
 83                    cx,
 84                );
 85                pane.set_can_split(None);
 86                pane.set_can_navigate(true, cx);
 87                pane.display_nav_history_buttons(None);
 88                pane.set_should_display_tab_bar(|_window, _cx| false);
 89                pane.set_close_pane_if_empty(true, cx);
 90
 91                pane
 92            });
 93
 94            let _subscriptions = vec![
 95                cx.observe(&pane, |_, _, cx| cx.notify()),
 96                cx.subscribe_in(&pane, window, Self::handle_pane_event),
 97                cx.subscribe_in(&dap_store, window, Self::handle_dap_store_event),
 98            ];
 99
100            let debug_panel = Self {
101                pane,
102                size: px(300.),
103                _subscriptions,
104                past_debug_definition: None,
105                project: project.downgrade(),
106                workspace: workspace.weak_handle(),
107            };
108
109            debug_panel
110        })
111    }
112
113    pub fn load(
114        workspace: WeakEntity<Workspace>,
115        cx: AsyncWindowContext,
116    ) -> Task<Result<Entity<Self>>> {
117        cx.spawn(async move |cx| {
118            workspace.update_in(cx, |workspace, window, cx| {
119                let debug_panel = DebugPanel::new(workspace, window, cx);
120
121                workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| {
122                    workspace.project().read(cx).breakpoint_store().update(
123                        cx,
124                        |breakpoint_store, cx| {
125                            breakpoint_store.clear_breakpoints(cx);
126                        },
127                    )
128                });
129
130                cx.observe(&debug_panel, |_, debug_panel, cx| {
131                    let (has_active_session, supports_restart, support_step_back) = debug_panel
132                        .update(cx, |this, cx| {
133                            this.active_session(cx)
134                                .map(|item| {
135                                    let running = item.read(cx).mode().as_running().cloned();
136
137                                    match running {
138                                        Some(running) => {
139                                            let caps = running.read(cx).capabilities(cx);
140                                            (
141                                                true,
142                                                caps.supports_restart_request.unwrap_or_default(),
143                                                caps.supports_step_back.unwrap_or_default(),
144                                            )
145                                        }
146                                        None => (false, false, false),
147                                    }
148                                })
149                                .unwrap_or((false, false, false))
150                        });
151
152                    let filter = CommandPaletteFilter::global_mut(cx);
153                    let debugger_action_types = [
154                        TypeId::of::<Continue>(),
155                        TypeId::of::<StepOver>(),
156                        TypeId::of::<StepInto>(),
157                        TypeId::of::<StepOut>(),
158                        TypeId::of::<Stop>(),
159                        TypeId::of::<Disconnect>(),
160                        TypeId::of::<Pause>(),
161                        TypeId::of::<ToggleIgnoreBreakpoints>(),
162                    ];
163
164                    let step_back_action_type = [TypeId::of::<StepBack>()];
165                    let restart_action_type = [TypeId::of::<Restart>()];
166
167                    if has_active_session {
168                        filter.show_action_types(debugger_action_types.iter());
169
170                        if supports_restart {
171                            filter.show_action_types(restart_action_type.iter());
172                        } else {
173                            filter.hide_action_types(&restart_action_type);
174                        }
175
176                        if support_step_back {
177                            filter.show_action_types(step_back_action_type.iter());
178                        } else {
179                            filter.hide_action_types(&step_back_action_type);
180                        }
181                    } else {
182                        // show only the `debug: start`
183                        filter.hide_action_types(&debugger_action_types);
184                        filter.hide_action_types(&step_back_action_type);
185                        filter.hide_action_types(&restart_action_type);
186                    }
187                })
188                .detach();
189
190                debug_panel
191            })
192        })
193    }
194
195    pub fn active_session(&self, cx: &App) -> Option<Entity<DebugSession>> {
196        self.pane
197            .read(cx)
198            .active_item()
199            .and_then(|panel| panel.downcast::<DebugSession>())
200    }
201
202    pub fn debug_panel_items_by_client(
203        &self,
204        client_id: &SessionId,
205        cx: &Context<Self>,
206    ) -> Vec<Entity<DebugSession>> {
207        self.pane
208            .read(cx)
209            .items()
210            .filter_map(|item| item.downcast::<DebugSession>())
211            .filter(|item| item.read(cx).session_id(cx) == Some(*client_id))
212            .map(|item| item.clone())
213            .collect()
214    }
215
216    pub fn debug_panel_item_by_client(
217        &self,
218        client_id: SessionId,
219        cx: &mut Context<Self>,
220    ) -> Option<Entity<DebugSession>> {
221        self.pane
222            .read(cx)
223            .items()
224            .filter_map(|item| item.downcast::<DebugSession>())
225            .find(|item| {
226                let item = item.read(cx);
227
228                item.session_id(cx) == Some(client_id)
229            })
230    }
231
232    fn handle_dap_store_event(
233        &mut self,
234        dap_store: &Entity<DapStore>,
235        event: &dap_store::DapStoreEvent,
236        window: &mut Window,
237        cx: &mut Context<Self>,
238    ) {
239        match event {
240            dap_store::DapStoreEvent::DebugSessionInitialized(session_id) => {
241                let Some(session) = dap_store.read(cx).session_by_id(session_id) else {
242                    return log::error!(
243                        "Couldn't get session with id: {session_id:?} from DebugClientStarted event"
244                    );
245                };
246
247                let Some(project) = self.project.upgrade() else {
248                    return log::error!("Debug Panel out lived it's weak reference to Project");
249                };
250
251                if self.pane.read_with(cx, |pane, cx| {
252                    pane.items_of_type::<DebugSession>()
253                        .any(|item| item.read(cx).session_id(cx) == Some(*session_id))
254                }) {
255                    // We already have an item for this session.
256                    return;
257                }
258                let session_item = DebugSession::running(
259                    project,
260                    self.workspace.clone(),
261                    session,
262                    cx.weak_entity(),
263                    window,
264                    cx,
265                );
266
267                self.pane.update(cx, |pane, cx| {
268                    pane.add_item(Box::new(session_item), true, true, None, window, cx);
269                    window.focus(&pane.focus_handle(cx));
270                    cx.notify();
271                });
272            }
273            dap_store::DapStoreEvent::RunInTerminal {
274                title,
275                cwd,
276                command,
277                args,
278                envs,
279                sender,
280                ..
281            } => {
282                self.handle_run_in_terminal_request(
283                    title.clone(),
284                    cwd.clone(),
285                    command.clone(),
286                    args.clone(),
287                    envs.clone(),
288                    sender.clone(),
289                    window,
290                    cx,
291                )
292                .detach_and_log_err(cx);
293            }
294            _ => {}
295        }
296    }
297
298    fn handle_run_in_terminal_request(
299        &self,
300        title: Option<String>,
301        cwd: PathBuf,
302        command: Option<String>,
303        args: Vec<String>,
304        envs: HashMap<String, String>,
305        mut sender: mpsc::Sender<Result<u32>>,
306        window: &mut Window,
307        cx: &mut App,
308    ) -> Task<Result<()>> {
309        let terminal_task = self.workspace.update(cx, |workspace, cx| {
310            let terminal_panel = workspace.panel::<TerminalPanel>(cx).ok_or_else(|| {
311                anyhow!("RunInTerminal DAP request failed because TerminalPanel wasn't found")
312            });
313
314            let terminal_panel = match terminal_panel {
315                Ok(panel) => panel,
316                Err(err) => return Task::ready(Err(err)),
317            };
318
319            terminal_panel.update(cx, |terminal_panel, cx| {
320                let terminal_task = terminal_panel.add_terminal(
321                    TerminalKind::Debug {
322                        command,
323                        args,
324                        envs,
325                        cwd,
326                        title,
327                    },
328                    task::RevealStrategy::Always,
329                    window,
330                    cx,
331                );
332
333                cx.spawn(async move |_, cx| {
334                    let pid_task = async move {
335                        let terminal = terminal_task.await?;
336
337                        terminal.read_with(cx, |terminal, _| terminal.pty_info.pid())
338                    };
339
340                    pid_task.await
341                })
342            })
343        });
344
345        cx.background_spawn(async move {
346            match terminal_task {
347                Ok(pid_task) => match pid_task.await {
348                    Ok(Some(pid)) => sender.send(Ok(pid.as_u32())).await?,
349                    Ok(None) => {
350                        sender
351                            .send(Err(anyhow!(
352                                "Terminal was spawned but PID was not available"
353                            )))
354                            .await?
355                    }
356                    Err(error) => sender.send(Err(anyhow!(error))).await?,
357                },
358                Err(error) => sender.send(Err(anyhow!(error))).await?,
359            };
360
361            Ok(())
362        })
363    }
364
365    fn handle_pane_event(
366        &mut self,
367        _: &Entity<Pane>,
368        event: &pane::Event,
369        window: &mut Window,
370        cx: &mut Context<Self>,
371    ) {
372        match event {
373            pane::Event::Remove { .. } => cx.emit(PanelEvent::Close),
374            pane::Event::ZoomIn => cx.emit(PanelEvent::ZoomIn),
375            pane::Event::ZoomOut => cx.emit(PanelEvent::ZoomOut),
376            pane::Event::AddItem { item } => {
377                self.workspace
378                    .update(cx, |workspace, cx| {
379                        item.added_to_pane(workspace, self.pane.clone(), window, cx)
380                    })
381                    .ok();
382            }
383            pane::Event::RemovedItem { item } => {
384                if let Some(debug_session) = item.downcast::<DebugSession>() {
385                    debug_session.update(cx, |session, cx| {
386                        session.shutdown(cx);
387                    })
388                }
389            }
390            pane::Event::ActivateItem {
391                local: _,
392                focus_changed,
393            } => {
394                if *focus_changed {
395                    if let Some(debug_session) = self
396                        .pane
397                        .read(cx)
398                        .active_item()
399                        .and_then(|item| item.downcast::<DebugSession>())
400                    {
401                        if let Some(running) = debug_session
402                            .read_with(cx, |session, _| session.mode().as_running().cloned())
403                        {
404                            running.update(cx, |running, cx| {
405                                running.go_to_selected_stack_frame(window, cx);
406                            });
407                        }
408                    }
409                }
410            }
411
412            _ => {}
413        }
414    }
415
416    fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
417        let active_session = self
418            .pane
419            .read(cx)
420            .active_item()
421            .and_then(|item| item.downcast::<DebugSession>());
422        Some(
423            h_flex()
424                .border_b_1()
425                .border_color(cx.theme().colors().border)
426                .p_1()
427                .justify_between()
428                .w_full()
429                .child(
430                    h_flex().gap_2().w_full().when_some(
431                        active_session
432                            .as_ref()
433                            .and_then(|session| session.read(cx).mode().as_running()),
434                        |this, running_session| {
435                            let thread_status = running_session
436                                .read(cx)
437                                .thread_status(cx)
438                                .unwrap_or(project::debugger::session::ThreadStatus::Exited);
439                            let capabilities = running_session.read(cx).capabilities(cx);
440                            this.map(|this| {
441                                if thread_status == ThreadStatus::Running {
442                                    this.child(
443                                        IconButton::new("debug-pause", IconName::DebugPause)
444                                            .icon_size(IconSize::XSmall)
445                                            .shape(ui::IconButtonShape::Square)
446                                            .on_click(window.listener_for(
447                                                &running_session,
448                                                |this, _, _window, cx| {
449                                                    this.pause_thread(cx);
450                                                },
451                                            ))
452                                            .tooltip(move |window, cx| {
453                                                Tooltip::text("Pause program")(window, cx)
454                                            }),
455                                    )
456                                } else {
457                                    this.child(
458                                        IconButton::new("debug-continue", IconName::DebugContinue)
459                                            .icon_size(IconSize::XSmall)
460                                            .shape(ui::IconButtonShape::Square)
461                                            .on_click(window.listener_for(
462                                                &running_session,
463                                                |this, _, _window, cx| this.continue_thread(cx),
464                                            ))
465                                            .disabled(thread_status != ThreadStatus::Stopped)
466                                            .tooltip(move |window, cx| {
467                                                Tooltip::text("Continue program")(window, cx)
468                                            }),
469                                    )
470                                }
471                            })
472                            .child(
473                                IconButton::new("debug-step-over", IconName::ArrowRight)
474                                    .icon_size(IconSize::XSmall)
475                                    .shape(ui::IconButtonShape::Square)
476                                    .on_click(window.listener_for(
477                                        &running_session,
478                                        |this, _, _window, cx| {
479                                            this.step_over(cx);
480                                        },
481                                    ))
482                                    .disabled(thread_status != ThreadStatus::Stopped)
483                                    .tooltip(move |window, cx| {
484                                        Tooltip::text("Step over")(window, cx)
485                                    }),
486                            )
487                            .child(
488                                IconButton::new("debug-step-out", IconName::ArrowUpRight)
489                                    .icon_size(IconSize::XSmall)
490                                    .shape(ui::IconButtonShape::Square)
491                                    .on_click(window.listener_for(
492                                        &running_session,
493                                        |this, _, _window, cx| {
494                                            this.step_out(cx);
495                                        },
496                                    ))
497                                    .disabled(thread_status != ThreadStatus::Stopped)
498                                    .tooltip(move |window, cx| {
499                                        Tooltip::text("Step out")(window, cx)
500                                    }),
501                            )
502                            .child(
503                                IconButton::new("debug-step-into", IconName::ArrowDownRight)
504                                    .icon_size(IconSize::XSmall)
505                                    .shape(ui::IconButtonShape::Square)
506                                    .on_click(window.listener_for(
507                                        &running_session,
508                                        |this, _, _window, cx| {
509                                            this.step_in(cx);
510                                        },
511                                    ))
512                                    .disabled(thread_status != ThreadStatus::Stopped)
513                                    .tooltip(move |window, cx| {
514                                        Tooltip::text("Step in")(window, cx)
515                                    }),
516                            )
517                            .child(Divider::vertical())
518                            .child(
519                                IconButton::new(
520                                    "debug-enable-breakpoint",
521                                    IconName::DebugDisabledBreakpoint,
522                                )
523                                .icon_size(IconSize::XSmall)
524                                .shape(ui::IconButtonShape::Square)
525                                .disabled(thread_status != ThreadStatus::Stopped),
526                            )
527                            .child(
528                                IconButton::new("debug-disable-breakpoint", IconName::CircleOff)
529                                    .icon_size(IconSize::XSmall)
530                                    .shape(ui::IconButtonShape::Square)
531                                    .disabled(thread_status != ThreadStatus::Stopped),
532                            )
533                            .child(
534                                IconButton::new("debug-disable-all-breakpoints", IconName::BugOff)
535                                    .icon_size(IconSize::XSmall)
536                                    .shape(ui::IconButtonShape::Square)
537                                    .disabled(
538                                        thread_status == ThreadStatus::Exited
539                                            || thread_status == ThreadStatus::Ended,
540                                    )
541                                    .on_click(window.listener_for(
542                                        &running_session,
543                                        |this, _, _window, cx| {
544                                            this.toggle_ignore_breakpoints(cx);
545                                        },
546                                    ))
547                                    .tooltip(move |window, cx| {
548                                        Tooltip::text("Disable all breakpoints")(window, cx)
549                                    }),
550                            )
551                            .child(Divider::vertical())
552                            .child(
553                                IconButton::new("debug-restart", IconName::DebugRestart)
554                                    .icon_size(IconSize::XSmall)
555                                    .on_click(window.listener_for(
556                                        &running_session,
557                                        |this, _, _window, cx| {
558                                            this.restart_session(cx);
559                                        },
560                                    ))
561                                    .disabled(
562                                        !capabilities.supports_restart_request.unwrap_or_default(),
563                                    )
564                                    .tooltip(move |window, cx| {
565                                        Tooltip::text("Restart")(window, cx)
566                                    }),
567                            )
568                            .child(
569                                IconButton::new("debug-stop", IconName::Power)
570                                    .icon_size(IconSize::XSmall)
571                                    .on_click(window.listener_for(
572                                        &running_session,
573                                        |this, _, _window, cx| {
574                                            this.stop_thread(cx);
575                                        },
576                                    ))
577                                    .disabled(
578                                        thread_status != ThreadStatus::Stopped
579                                            && thread_status != ThreadStatus::Running,
580                                    )
581                                    .tooltip({
582                                        let label = if capabilities
583                                            .supports_terminate_threads_request
584                                            .unwrap_or_default()
585                                        {
586                                            "Terminate Thread"
587                                        } else {
588                                            "Terminate all Threads"
589                                        };
590                                        move |window, cx| Tooltip::text(label)(window, cx)
591                                    }),
592                            )
593                        },
594                    ),
595                )
596                .child(
597                    h_flex()
598                        .gap_2()
599                        .when_some(
600                            active_session
601                                .as_ref()
602                                .and_then(|session| session.read(cx).mode().as_running())
603                                .cloned(),
604                            |this, session| {
605                                this.child(
606                                    session.update(cx, |this, cx| this.thread_dropdown(window, cx)),
607                                )
608                                .child(Divider::vertical())
609                            },
610                        )
611                        .when_some(active_session.as_ref(), |this, session| {
612                            let pane = self.pane.downgrade();
613                            let label = session.read(cx).label(cx);
614                            this.child(DropdownMenu::new(
615                                "debugger-session-list",
616                                label,
617                                ContextMenu::build(window, cx, move |mut this, _, cx| {
618                                    let sessions = pane
619                                        .read_with(cx, |pane, _| {
620                                            pane.items().map(|item| item.boxed_clone()).collect()
621                                        })
622                                        .ok()
623                                        .unwrap_or_else(Vec::new);
624                                    for (index, item) in sessions.into_iter().enumerate() {
625                                        if let Some(session) = item.downcast::<DebugSession>() {
626                                            let pane = pane.clone();
627                                            this = this.entry(
628                                                session.read(cx).label(cx),
629                                                None,
630                                                move |window, cx| {
631                                                    pane.update(cx, |pane, cx| {
632                                                        pane.activate_item(
633                                                            index, true, true, window, cx,
634                                                        );
635                                                    })
636                                                    .ok();
637                                                },
638                                            );
639                                        }
640                                    }
641                                    this
642                                }),
643                            ))
644                            .child(Divider::vertical())
645                        })
646                        .child(
647                            IconButton::new("debug-new-session", IconName::Plus)
648                                .icon_size(IconSize::Small)
649                                .on_click({
650                                    let workspace = self.workspace.clone();
651                                    let weak_panel = cx.weak_entity();
652                                    let past_debug_definition = self.past_debug_definition.clone();
653                                    move |_, window, cx| {
654                                        let weak_panel = weak_panel.clone();
655                                        let past_debug_definition = past_debug_definition.clone();
656
657                                        let _ = workspace.update(cx, |this, cx| {
658                                            let workspace = cx.weak_entity();
659                                            this.toggle_modal(window, cx, |window, cx| {
660                                                NewSessionModal::new(
661                                                    past_debug_definition,
662                                                    weak_panel,
663                                                    workspace,
664                                                    window,
665                                                    cx,
666                                                )
667                                            });
668                                        });
669                                    }
670                                })
671                                .tooltip(|window, cx| {
672                                    Tooltip::for_action(
673                                        "New Debug Session",
674                                        &CreateDebuggingSession,
675                                        window,
676                                        cx,
677                                    )
678                                }),
679                        ),
680                ),
681        )
682    }
683}
684
685impl EventEmitter<PanelEvent> for DebugPanel {}
686impl EventEmitter<DebugPanelEvent> for DebugPanel {}
687impl EventEmitter<project::Event> for DebugPanel {}
688
689impl Focusable for DebugPanel {
690    fn focus_handle(&self, cx: &App) -> FocusHandle {
691        self.pane.focus_handle(cx)
692    }
693}
694
695impl Panel for DebugPanel {
696    fn pane(&self) -> Option<Entity<Pane>> {
697        Some(self.pane.clone())
698    }
699
700    fn persistent_name() -> &'static str {
701        "DebugPanel"
702    }
703
704    fn position(&self, _window: &Window, _cx: &App) -> DockPosition {
705        DockPosition::Bottom
706    }
707
708    fn position_is_valid(&self, position: DockPosition) -> bool {
709        position == DockPosition::Bottom
710    }
711
712    fn set_position(
713        &mut self,
714        _position: DockPosition,
715        _window: &mut Window,
716        _cx: &mut Context<Self>,
717    ) {
718    }
719
720    fn size(&self, _window: &Window, _: &App) -> Pixels {
721        self.size
722    }
723
724    fn set_size(&mut self, size: Option<Pixels>, _window: &mut Window, _cx: &mut Context<Self>) {
725        self.size = size.unwrap();
726    }
727
728    fn remote_id() -> Option<proto::PanelId> {
729        Some(proto::PanelId::DebugPanel)
730    }
731
732    fn icon(&self, _window: &Window, _cx: &App) -> Option<IconName> {
733        Some(IconName::Debug)
734    }
735
736    fn icon_tooltip(&self, _window: &Window, cx: &App) -> Option<&'static str> {
737        if DebuggerSettings::get_global(cx).button {
738            Some("Debug Panel")
739        } else {
740            None
741        }
742    }
743
744    fn toggle_action(&self) -> Box<dyn Action> {
745        Box::new(ToggleFocus)
746    }
747
748    fn activation_priority(&self) -> u32 {
749        9
750    }
751    fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
752}
753
754impl Render for DebugPanel {
755    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
756        let has_sessions = self.pane.read(cx).items_len() > 0;
757        v_flex()
758            .size_full()
759            .key_context("DebugPanel")
760            .child(h_flex().children(self.top_controls_strip(window, cx)))
761            .track_focus(&self.focus_handle(cx))
762            .map(|this| {
763                if has_sessions {
764                    this.child(self.pane.clone())
765                } else {
766                    this.child(
767                        v_flex()
768                            .h_full()
769                            .gap_1()
770                            .items_center()
771                            .justify_center()
772                            .child(
773                                h_flex().child(
774                                    Label::new("No Debugging Sessions")
775                                        .size(LabelSize::Small)
776                                        .color(Color::Muted),
777                                ),
778                            )
779                            .child(
780                                h_flex().flex_shrink().child(
781                                    Button::new("spawn-new-session-empty-state", "New Session")
782                                        .size(ButtonSize::Large)
783                                        .on_click(|_, window, cx| {
784                                            window.dispatch_action(
785                                                CreateDebuggingSession.boxed_clone(),
786                                                cx,
787                                            );
788                                        }),
789                                ),
790                            ),
791                    )
792                }
793            })
794            .into_any()
795    }
796}