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