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, _, cx| {
421                let context_menu = cx.weak_entity();
422                for session in sessions.into_iter() {
423                    let weak_session = session.downgrade();
424                    let weak_session_id = weak_session.entity_id();
425
426                    this = this.custom_entry(
427                        {
428                            let weak = weak.clone();
429                            let context_menu = context_menu.clone();
430                            move |_, cx| {
431                                weak_session
432                                    .read_with(cx, |session, cx| {
433                                        let context_menu = context_menu.clone();
434                                        let id: SharedString =
435                                            format!("debug-session-{}", session.session_id(cx).0)
436                                                .into();
437                                        h_flex()
438                                            .w_full()
439                                            .group(id.clone())
440                                            .justify_between()
441                                            .child(session.label_element(cx))
442                                            .child(
443                                                IconButton::new(
444                                                    "close-debug-session",
445                                                    IconName::Close,
446                                                )
447                                                .visible_on_hover(id.clone())
448                                                .icon_size(IconSize::Small)
449                                                .on_click({
450                                                    let weak = weak.clone();
451                                                    move |_, window, cx| {
452                                                        weak.update(cx, |panel, cx| {
453                                                            panel
454                                                                .close_session(weak_session_id, cx);
455                                                        })
456                                                        .ok();
457                                                        context_menu
458                                                            .update(cx, |this, cx| {
459                                                                this.cancel(
460                                                                    &Default::default(),
461                                                                    window,
462                                                                    cx,
463                                                                );
464                                                            })
465                                                            .ok();
466                                                    }
467                                                }),
468                                            )
469                                            .into_any_element()
470                                    })
471                                    .unwrap_or_else(|_| div().into_any_element())
472                            }
473                        },
474                        {
475                            let weak = weak.clone();
476                            move |window, cx| {
477                                weak.update(cx, |panel, cx| {
478                                    panel.activate_session(session.clone(), window, cx);
479                                })
480                                .ok();
481                            }
482                        },
483                    );
484                }
485                this
486            }),
487        )
488    }
489
490    fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
491        let active_session = self.active_session.clone();
492
493        Some(
494            h_flex()
495                .border_b_1()
496                .border_color(cx.theme().colors().border)
497                .p_1()
498                .justify_between()
499                .w_full()
500                .child(
501                    h_flex().gap_2().w_full().when_some(
502                        active_session
503                            .as_ref()
504                            .and_then(|session| session.read(cx).mode().as_running()),
505                        |this, running_session| {
506                            let thread_status = running_session
507                                .read(cx)
508                                .thread_status(cx)
509                                .unwrap_or(project::debugger::session::ThreadStatus::Exited);
510                            let capabilities = running_session.read(cx).capabilities(cx);
511                            this.map(|this| {
512                                if thread_status == ThreadStatus::Running {
513                                    this.child(
514                                        IconButton::new("debug-pause", IconName::DebugPause)
515                                            .icon_size(IconSize::XSmall)
516                                            .shape(ui::IconButtonShape::Square)
517                                            .on_click(window.listener_for(
518                                                &running_session,
519                                                |this, _, _window, cx| {
520                                                    this.pause_thread(cx);
521                                                },
522                                            ))
523                                            .tooltip(move |window, cx| {
524                                                Tooltip::text("Pause program")(window, cx)
525                                            }),
526                                    )
527                                } else {
528                                    this.child(
529                                        IconButton::new("debug-continue", IconName::DebugContinue)
530                                            .icon_size(IconSize::XSmall)
531                                            .shape(ui::IconButtonShape::Square)
532                                            .on_click(window.listener_for(
533                                                &running_session,
534                                                |this, _, _window, cx| this.continue_thread(cx),
535                                            ))
536                                            .disabled(thread_status != ThreadStatus::Stopped)
537                                            .tooltip(move |window, cx| {
538                                                Tooltip::text("Continue program")(window, cx)
539                                            }),
540                                    )
541                                }
542                            })
543                            .child(
544                                IconButton::new("debug-step-over", IconName::ArrowRight)
545                                    .icon_size(IconSize::XSmall)
546                                    .shape(ui::IconButtonShape::Square)
547                                    .on_click(window.listener_for(
548                                        &running_session,
549                                        |this, _, _window, cx| {
550                                            this.step_over(cx);
551                                        },
552                                    ))
553                                    .disabled(thread_status != ThreadStatus::Stopped)
554                                    .tooltip(move |window, cx| {
555                                        Tooltip::text("Step over")(window, cx)
556                                    }),
557                            )
558                            .child(
559                                IconButton::new("debug-step-out", IconName::ArrowUpRight)
560                                    .icon_size(IconSize::XSmall)
561                                    .shape(ui::IconButtonShape::Square)
562                                    .on_click(window.listener_for(
563                                        &running_session,
564                                        |this, _, _window, cx| {
565                                            this.step_out(cx);
566                                        },
567                                    ))
568                                    .disabled(thread_status != ThreadStatus::Stopped)
569                                    .tooltip(move |window, cx| {
570                                        Tooltip::text("Step out")(window, cx)
571                                    }),
572                            )
573                            .child(
574                                IconButton::new("debug-step-into", IconName::ArrowDownRight)
575                                    .icon_size(IconSize::XSmall)
576                                    .shape(ui::IconButtonShape::Square)
577                                    .on_click(window.listener_for(
578                                        &running_session,
579                                        |this, _, _window, cx| {
580                                            this.step_in(cx);
581                                        },
582                                    ))
583                                    .disabled(thread_status != ThreadStatus::Stopped)
584                                    .tooltip(move |window, cx| {
585                                        Tooltip::text("Step in")(window, cx)
586                                    }),
587                            )
588                            .child(Divider::vertical())
589                            .child(
590                                IconButton::new(
591                                    "debug-enable-breakpoint",
592                                    IconName::DebugDisabledBreakpoint,
593                                )
594                                .icon_size(IconSize::XSmall)
595                                .shape(ui::IconButtonShape::Square)
596                                .disabled(thread_status != ThreadStatus::Stopped),
597                            )
598                            .child(
599                                IconButton::new("debug-disable-breakpoint", IconName::CircleOff)
600                                    .icon_size(IconSize::XSmall)
601                                    .shape(ui::IconButtonShape::Square)
602                                    .disabled(thread_status != ThreadStatus::Stopped),
603                            )
604                            .child(
605                                IconButton::new("debug-disable-all-breakpoints", IconName::BugOff)
606                                    .icon_size(IconSize::XSmall)
607                                    .shape(ui::IconButtonShape::Square)
608                                    .disabled(
609                                        thread_status == ThreadStatus::Exited
610                                            || thread_status == ThreadStatus::Ended,
611                                    )
612                                    .on_click(window.listener_for(
613                                        &running_session,
614                                        |this, _, _window, cx| {
615                                            this.toggle_ignore_breakpoints(cx);
616                                        },
617                                    ))
618                                    .tooltip(move |window, cx| {
619                                        Tooltip::text("Disable all breakpoints")(window, cx)
620                                    }),
621                            )
622                            .child(Divider::vertical())
623                            .child(
624                                IconButton::new("debug-restart", IconName::DebugRestart)
625                                    .icon_size(IconSize::XSmall)
626                                    .on_click(window.listener_for(
627                                        &running_session,
628                                        |this, _, _window, cx| {
629                                            this.restart_session(cx);
630                                        },
631                                    ))
632                                    .disabled(
633                                        !capabilities.supports_restart_request.unwrap_or_default(),
634                                    )
635                                    .tooltip(move |window, cx| {
636                                        Tooltip::text("Restart")(window, cx)
637                                    }),
638                            )
639                            .child(
640                                IconButton::new("debug-stop", IconName::Power)
641                                    .icon_size(IconSize::XSmall)
642                                    .on_click(window.listener_for(
643                                        &running_session,
644                                        |this, _, _window, cx| {
645                                            this.stop_thread(cx);
646                                        },
647                                    ))
648                                    .disabled(
649                                        thread_status != ThreadStatus::Stopped
650                                            && thread_status != ThreadStatus::Running,
651                                    )
652                                    .tooltip({
653                                        let label = if capabilities
654                                            .supports_terminate_threads_request
655                                            .unwrap_or_default()
656                                        {
657                                            "Terminate Thread"
658                                        } else {
659                                            "Terminate all Threads"
660                                        };
661                                        move |window, cx| Tooltip::text(label)(window, cx)
662                                    }),
663                            )
664                        },
665                    ),
666                )
667                .child(
668                    h_flex()
669                        .gap_2()
670                        .when_some(
671                            active_session
672                                .as_ref()
673                                .and_then(|session| session.read(cx).mode().as_running())
674                                .cloned(),
675                            |this, session| {
676                                this.child(
677                                    session.update(cx, |this, cx| this.thread_dropdown(window, cx)),
678                                )
679                                .child(Divider::vertical())
680                            },
681                        )
682                        .when_some(active_session.as_ref(), |this, session| {
683                            let context_menu = self.sessions_drop_down_menu(session, window, cx);
684                            this.child(context_menu).child(Divider::vertical())
685                        })
686                        .child(
687                            IconButton::new("debug-new-session", IconName::Plus)
688                                .icon_size(IconSize::Small)
689                                .on_click({
690                                    let workspace = self.workspace.clone();
691                                    let weak_panel = cx.weak_entity();
692                                    let past_debug_definition = self.past_debug_definition.clone();
693                                    move |_, window, cx| {
694                                        let weak_panel = weak_panel.clone();
695                                        let past_debug_definition = past_debug_definition.clone();
696
697                                        let _ = workspace.update(cx, |this, cx| {
698                                            let workspace = cx.weak_entity();
699                                            this.toggle_modal(window, cx, |window, cx| {
700                                                NewSessionModal::new(
701                                                    past_debug_definition,
702                                                    weak_panel,
703                                                    workspace,
704                                                    window,
705                                                    cx,
706                                                )
707                                            });
708                                        });
709                                    }
710                                })
711                                .tooltip(|window, cx| {
712                                    Tooltip::for_action(
713                                        "New Debug Session",
714                                        &CreateDebuggingSession,
715                                        window,
716                                        cx,
717                                    )
718                                }),
719                        ),
720                ),
721        )
722    }
723
724    fn activate_session(
725        &mut self,
726        session_item: Entity<DebugSession>,
727        window: &mut Window,
728        cx: &mut Context<Self>,
729    ) {
730        debug_assert!(self.sessions.contains(&session_item));
731        session_item.focus_handle(cx).focus(window);
732        session_item.update(cx, |this, cx| {
733            if let Some(running) = this.mode().as_running() {
734                running.update(cx, |this, cx| {
735                    this.go_to_selected_stack_frame(window, cx);
736                });
737            }
738        });
739        self.active_session = Some(session_item);
740        cx.notify();
741    }
742}
743
744impl EventEmitter<PanelEvent> for DebugPanel {}
745impl EventEmitter<DebugPanelEvent> for DebugPanel {}
746impl EventEmitter<project::Event> for DebugPanel {}
747
748impl Focusable for DebugPanel {
749    fn focus_handle(&self, _: &App) -> FocusHandle {
750        self.focus_handle.clone()
751    }
752}
753
754impl Panel for DebugPanel {
755    fn persistent_name() -> &'static str {
756        "DebugPanel"
757    }
758
759    fn position(&self, _window: &Window, _cx: &App) -> DockPosition {
760        DockPosition::Bottom
761    }
762
763    fn position_is_valid(&self, position: DockPosition) -> bool {
764        position == DockPosition::Bottom
765    }
766
767    fn set_position(
768        &mut self,
769        _position: DockPosition,
770        _window: &mut Window,
771        _cx: &mut Context<Self>,
772    ) {
773    }
774
775    fn size(&self, _window: &Window, _: &App) -> Pixels {
776        self.size
777    }
778
779    fn set_size(&mut self, size: Option<Pixels>, _window: &mut Window, _cx: &mut Context<Self>) {
780        self.size = size.unwrap();
781    }
782
783    fn remote_id() -> Option<proto::PanelId> {
784        Some(proto::PanelId::DebugPanel)
785    }
786
787    fn icon(&self, _window: &Window, _cx: &App) -> Option<IconName> {
788        Some(IconName::Debug)
789    }
790
791    fn icon_tooltip(&self, _window: &Window, cx: &App) -> Option<&'static str> {
792        if DebuggerSettings::get_global(cx).button {
793            Some("Debug Panel")
794        } else {
795            None
796        }
797    }
798
799    fn toggle_action(&self) -> Box<dyn Action> {
800        Box::new(ToggleFocus)
801    }
802
803    fn activation_priority(&self) -> u32 {
804        9
805    }
806    fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
807}
808
809impl Render for DebugPanel {
810    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
811        let has_sessions = self.sessions.len() > 0;
812        debug_assert_eq!(has_sessions, self.active_session.is_some());
813
814        v_flex()
815            .size_full()
816            .key_context("DebugPanel")
817            .child(h_flex().children(self.top_controls_strip(window, cx)))
818            .track_focus(&self.focus_handle(cx))
819            .map(|this| {
820                if has_sessions {
821                    this.children(self.active_session.clone())
822                } else {
823                    this.child(
824                        v_flex()
825                            .h_full()
826                            .gap_1()
827                            .items_center()
828                            .justify_center()
829                            .child(
830                                h_flex().child(
831                                    Label::new("No Debugging Sessions")
832                                        .size(LabelSize::Small)
833                                        .color(Color::Muted),
834                                ),
835                            )
836                            .child(
837                                h_flex().flex_shrink().child(
838                                    Button::new("spawn-new-session-empty-state", "New Session")
839                                        .size(ButtonSize::Large)
840                                        .on_click(|_, window, cx| {
841                                            window.dispatch_action(
842                                                CreateDebuggingSession.boxed_clone(),
843                                                cx,
844                                            );
845                                        }),
846                                ),
847                            ),
848                    )
849                }
850            })
851            .into_any()
852    }
853}