1use crate::persistence::DebuggerPaneItem;
2use crate::{
3 ClearAllBreakpoints, Continue, CreateDebuggingSession, Disconnect, FocusBreakpointList,
4 FocusConsole, FocusFrames, FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables,
5 Pause, Restart, StepBack, StepInto, StepOut, StepOver, Stop, ToggleIgnoreBreakpoints,
6 persistence,
7};
8use crate::{new_session_modal::NewSessionModal, session::DebugSession};
9use anyhow::Result;
10use command_palette_hooks::CommandPaletteFilter;
11use dap::adapters::DebugAdapterName;
12use dap::{
13 ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
14 client::SessionId, debugger_settings::DebuggerSettings,
15};
16use dap::{StartDebuggingRequestArguments, adapters::DebugTaskDefinition};
17use gpui::{
18 Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EntityId, EventEmitter,
19 FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task, WeakEntity,
20 actions, anchored, deferred,
21};
22
23use language::Buffer;
24use project::debugger::session::{Session, SessionStateEvent};
25use project::{Project, debugger::session::ThreadStatus};
26use rpc::proto::{self};
27use settings::Settings;
28use std::any::TypeId;
29use task::{DebugScenario, TaskContext};
30use ui::{ContextMenu, Divider, DropdownMenu, Tooltip, prelude::*};
31use workspace::SplitDirection;
32use workspace::{
33 Pane, Workspace,
34 dock::{DockPosition, Panel, PanelEvent},
35};
36
37pub enum DebugPanelEvent {
38 Exited(SessionId),
39 Terminated(SessionId),
40 Stopped {
41 client_id: SessionId,
42 event: StoppedEvent,
43 go_to_stack_frame: bool,
44 },
45 Thread((SessionId, ThreadEvent)),
46 Continued((SessionId, ContinuedEvent)),
47 Output((SessionId, OutputEvent)),
48 Module((SessionId, ModuleEvent)),
49 LoadedSource((SessionId, LoadedSourceEvent)),
50 ClientShutdown(SessionId),
51 CapabilitiesChanged(SessionId),
52}
53
54actions!(debug_panel, [ToggleFocus]);
55pub struct DebugPanel {
56 size: Pixels,
57 sessions: Vec<Entity<DebugSession>>,
58 active_session: Option<Entity<DebugSession>>,
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: Entity<Project>,
62 workspace: WeakEntity<Workspace>,
63 focus_handle: FocusHandle,
64 context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
65}
66
67impl DebugPanel {
68 pub fn new(
69 workspace: &Workspace,
70 _window: &mut Window,
71 cx: &mut Context<Workspace>,
72 ) -> Entity<Self> {
73 cx.new(|cx| {
74 let project = workspace.project().clone();
75
76 let debug_panel = Self {
77 size: px(300.),
78 sessions: vec![],
79 active_session: None,
80 past_debug_definition: None,
81 focus_handle: cx.focus_handle(),
82 project,
83 workspace: workspace.weak_handle(),
84 context_menu: None,
85 };
86
87 debug_panel
88 })
89 }
90
91 fn filter_action_types(&self, cx: &mut App) {
92 let (has_active_session, supports_restart, support_step_back, status) = self
93 .active_session()
94 .map(|item| {
95 let running = item.read(cx).running_state().clone();
96 let caps = running.read(cx).capabilities(cx);
97 (
98 !running.read(cx).session().read(cx).is_terminated(),
99 caps.supports_restart_request.unwrap_or_default(),
100 caps.supports_step_back.unwrap_or_default(),
101 running.read(cx).thread_status(cx),
102 )
103 })
104 .unwrap_or((false, false, false, None));
105
106 let filter = CommandPaletteFilter::global_mut(cx);
107 let debugger_action_types = [
108 TypeId::of::<Disconnect>(),
109 TypeId::of::<Stop>(),
110 TypeId::of::<ToggleIgnoreBreakpoints>(),
111 ];
112
113 let running_action_types = [TypeId::of::<Pause>()];
114
115 let stopped_action_type = [
116 TypeId::of::<Continue>(),
117 TypeId::of::<StepOver>(),
118 TypeId::of::<StepInto>(),
119 TypeId::of::<StepOut>(),
120 TypeId::of::<editor::actions::DebuggerRunToCursor>(),
121 TypeId::of::<editor::actions::DebuggerEvaluateSelectedText>(),
122 ];
123
124 let step_back_action_type = [TypeId::of::<StepBack>()];
125 let restart_action_type = [TypeId::of::<Restart>()];
126
127 if has_active_session {
128 filter.show_action_types(debugger_action_types.iter());
129
130 if supports_restart {
131 filter.show_action_types(restart_action_type.iter());
132 } else {
133 filter.hide_action_types(&restart_action_type);
134 }
135
136 if support_step_back {
137 filter.show_action_types(step_back_action_type.iter());
138 } else {
139 filter.hide_action_types(&step_back_action_type);
140 }
141
142 match status {
143 Some(ThreadStatus::Running) => {
144 filter.show_action_types(running_action_types.iter());
145 filter.hide_action_types(&stopped_action_type);
146 }
147 Some(ThreadStatus::Stopped) => {
148 filter.show_action_types(stopped_action_type.iter());
149 filter.hide_action_types(&running_action_types);
150 }
151 _ => {
152 filter.hide_action_types(&running_action_types);
153 filter.hide_action_types(&stopped_action_type);
154 }
155 }
156 } else {
157 // show only the `debug: start`
158 filter.hide_action_types(&debugger_action_types);
159 filter.hide_action_types(&step_back_action_type);
160 filter.hide_action_types(&restart_action_type);
161 filter.hide_action_types(&running_action_types);
162 filter.hide_action_types(&stopped_action_type);
163 }
164 }
165
166 pub fn load(
167 workspace: WeakEntity<Workspace>,
168 cx: &mut AsyncWindowContext,
169 ) -> Task<Result<Entity<Self>>> {
170 cx.spawn(async move |cx| {
171 workspace.update_in(cx, |workspace, window, cx| {
172 let debug_panel = DebugPanel::new(workspace, window, cx);
173
174 workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| {
175 workspace.project().read(cx).breakpoint_store().update(
176 cx,
177 |breakpoint_store, cx| {
178 breakpoint_store.clear_breakpoints(cx);
179 },
180 )
181 });
182
183 cx.observe_new::<DebugPanel>(|debug_panel, _, cx| {
184 Self::filter_action_types(debug_panel, cx);
185 })
186 .detach();
187
188 cx.observe(&debug_panel, |_, debug_panel, cx| {
189 debug_panel.update(cx, |debug_panel, cx| {
190 Self::filter_action_types(debug_panel, cx);
191 });
192 })
193 .detach();
194 workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));
195
196 debug_panel
197 })
198 })
199 }
200
201 pub fn start_session(
202 &mut self,
203 scenario: DebugScenario,
204 task_context: TaskContext,
205 active_buffer: Option<Entity<Buffer>>,
206 window: &mut Window,
207 cx: &mut Context<Self>,
208 ) {
209 let dap_store = self.project.read(cx).dap_store();
210 let workspace = self.workspace.clone();
211 let session = dap_store.update(cx, |dap_store, cx| {
212 dap_store.new_session(
213 scenario.label.clone(),
214 DebugAdapterName(scenario.adapter.clone()),
215 None,
216 cx,
217 )
218 });
219 let task = cx.spawn_in(window, {
220 let session = session.clone();
221 async move |this, cx| {
222 let debug_session =
223 Self::register_session(this.clone(), session.clone(), cx).await?;
224 let definition = debug_session
225 .update_in(cx, |debug_session, window, cx| {
226 debug_session.running_state().update(cx, |running, cx| {
227 running.resolve_scenario(
228 scenario,
229 task_context,
230 active_buffer,
231 window,
232 cx,
233 )
234 })
235 })?
236 .await?;
237
238 dap_store
239 .update(cx, |dap_store, cx| {
240 dap_store.boot_session(session.clone(), definition, cx)
241 })?
242 .await
243 }
244 });
245
246 cx.spawn(async move |_, cx| {
247 if let Err(error) = task.await {
248 log::error!("{:?}", error);
249 workspace
250 .update(cx, |workspace, cx| {
251 workspace.show_error(&error, cx);
252 })
253 .ok();
254 session
255 .update(cx, |session, cx| session.shutdown(cx))?
256 .await;
257 }
258 anyhow::Ok(())
259 })
260 .detach_and_log_err(cx);
261 }
262
263 async fn register_session(
264 this: WeakEntity<Self>,
265 session: Entity<Session>,
266 cx: &mut AsyncWindowContext,
267 ) -> Result<Entity<DebugSession>> {
268 let adapter_name = session.update(cx, |session, _| session.adapter())?;
269 this.update_in(cx, |_, window, cx| {
270 cx.subscribe_in(
271 &session,
272 window,
273 move |this, session, event: &SessionStateEvent, window, cx| match event {
274 SessionStateEvent::Restart => {
275 this.handle_restart_request(session.clone(), window, cx);
276 }
277 SessionStateEvent::SpawnChildSession { request } => {
278 this.handle_start_debugging_request(request, session.clone(), window, cx);
279 }
280 _ => {}
281 },
282 )
283 .detach();
284 })
285 .ok();
286
287 let serialized_layout = persistence::get_serialized_pane_layout(adapter_name).await;
288
289 let (debug_session, workspace) = this.update_in(cx, |this, window, cx| {
290 this.sessions.retain(|session| {
291 session
292 .read(cx)
293 .running_state()
294 .read(cx)
295 .session()
296 .read(cx)
297 .is_terminated()
298 });
299
300 let debug_session = DebugSession::running(
301 this.project.clone(),
302 this.workspace.clone(),
303 session,
304 cx.weak_entity(),
305 serialized_layout,
306 window,
307 cx,
308 );
309
310 // We might want to make this an event subscription and only notify when a new thread is selected
311 // This is used to filter the command menu correctly
312 cx.observe(
313 &debug_session.read(cx).running_state().clone(),
314 |_, _, cx| cx.notify(),
315 )
316 .detach();
317
318 this.sessions.push(debug_session.clone());
319 this.activate_session(debug_session.clone(), window, cx);
320
321 (debug_session, this.workspace.clone())
322 })?;
323
324 workspace.update_in(cx, |workspace, window, cx| {
325 workspace.focus_panel::<Self>(window, cx);
326 })?;
327
328 Ok(debug_session)
329 }
330
331 fn handle_restart_request(
332 &mut self,
333 mut curr_session: Entity<Session>,
334 window: &mut Window,
335 cx: &mut Context<Self>,
336 ) {
337 while let Some(parent_session) =
338 curr_session.read_with(cx, |session, _| session.parent_session().cloned())
339 {
340 curr_session = parent_session;
341 }
342
343 let Some(worktree) = curr_session.read(cx).worktree() else {
344 log::error!("Attempted to start a child session from non local debug session");
345 return;
346 };
347
348 let dap_store_handle = self.project.read(cx).dap_store().clone();
349 let label = curr_session.read(cx).label().clone();
350 let adapter = curr_session.read(cx).adapter().clone();
351 let binary = curr_session.read(cx).binary().clone();
352 let task = curr_session.update(cx, |session, cx| session.shutdown(cx));
353
354 cx.spawn_in(window, async move |this, cx| {
355 task.await;
356
357 let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
358 let session = dap_store.new_session(label, adapter, None, cx);
359
360 let task = session.update(cx, |session, cx| {
361 session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
362 });
363 (session, task)
364 })?;
365 Self::register_session(this, session, cx).await?;
366 task.await
367 })
368 .detach_and_log_err(cx);
369 }
370
371 pub fn handle_start_debugging_request(
372 &mut self,
373 request: &StartDebuggingRequestArguments,
374 parent_session: Entity<Session>,
375 window: &mut Window,
376 cx: &mut Context<Self>,
377 ) {
378 let Some(worktree) = parent_session.read(cx).worktree() else {
379 log::error!("Attempted to start a child session from non local debug session");
380 return;
381 };
382
383 let dap_store_handle = self.project.read(cx).dap_store().clone();
384 let label = parent_session.read(cx).label().clone();
385 let adapter = parent_session.read(cx).adapter().clone();
386 let mut binary = parent_session.read(cx).binary().clone();
387 binary.request_args = request.clone();
388
389 cx.spawn_in(window, async move |this, cx| {
390 let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
391 let session =
392 dap_store.new_session(label, adapter, Some(parent_session.clone()), cx);
393
394 let task = session.update(cx, |session, cx| {
395 session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
396 });
397 (session, task)
398 })?;
399 Self::register_session(this, session, cx).await?;
400 task.await
401 })
402 .detach_and_log_err(cx);
403 }
404
405 pub fn active_session(&self) -> Option<Entity<DebugSession>> {
406 self.active_session.clone()
407 }
408 fn close_session(&mut self, entity_id: EntityId, window: &mut Window, cx: &mut Context<Self>) {
409 let Some(session) = self
410 .sessions
411 .iter()
412 .find(|other| entity_id == other.entity_id())
413 .cloned()
414 else {
415 return;
416 };
417 session.update(cx, |this, cx| {
418 this.running_state().update(cx, |this, cx| {
419 this.serialize_layout(window, cx);
420 });
421 });
422 let session_id = session.update(cx, |this, cx| this.session_id(cx));
423 let should_prompt = self
424 .project
425 .update(cx, |this, cx| {
426 let session = this.dap_store().read(cx).session_by_id(session_id);
427 session.map(|session| !session.read(cx).is_terminated())
428 })
429 .unwrap_or_default();
430
431 cx.spawn_in(window, async move |this, cx| {
432 if should_prompt {
433 let response = cx.prompt(
434 gpui::PromptLevel::Warning,
435 "This Debug Session is still running. Are you sure you want to terminate it?",
436 None,
437 &["Yes", "No"],
438 );
439 if response.await == Ok(1) {
440 return;
441 }
442 }
443 session.update(cx, |session, cx| session.shutdown(cx)).ok();
444 this.update(cx, |this, cx| {
445 this.sessions.retain(|other| entity_id != other.entity_id());
446
447 if let Some(active_session_id) = this
448 .active_session
449 .as_ref()
450 .map(|session| session.entity_id())
451 {
452 if active_session_id == entity_id {
453 this.active_session = this.sessions.first().cloned();
454 }
455 }
456 cx.notify()
457 })
458 .ok();
459 })
460 .detach();
461 }
462 fn sessions_drop_down_menu(
463 &self,
464 active_session: &Entity<DebugSession>,
465 window: &mut Window,
466 cx: &mut Context<Self>,
467 ) -> DropdownMenu {
468 let sessions = self.sessions.clone();
469 let weak = cx.weak_entity();
470 let label = active_session.read(cx).label_element(cx);
471
472 DropdownMenu::new_with_element(
473 "debugger-session-list",
474 label,
475 ContextMenu::build(window, cx, move |mut this, _, cx| {
476 let context_menu = cx.weak_entity();
477 for session in sessions.into_iter() {
478 let weak_session = session.downgrade();
479 let weak_session_id = weak_session.entity_id();
480
481 this = this.custom_entry(
482 {
483 let weak = weak.clone();
484 let context_menu = context_menu.clone();
485 move |_, cx| {
486 weak_session
487 .read_with(cx, |session, cx| {
488 let context_menu = context_menu.clone();
489 let id: SharedString =
490 format!("debug-session-{}", session.session_id(cx).0)
491 .into();
492 h_flex()
493 .w_full()
494 .group(id.clone())
495 .justify_between()
496 .child(session.label_element(cx))
497 .child(
498 IconButton::new(
499 "close-debug-session",
500 IconName::Close,
501 )
502 .visible_on_hover(id.clone())
503 .icon_size(IconSize::Small)
504 .on_click({
505 let weak = weak.clone();
506 move |_, window, cx| {
507 weak.update(cx, |panel, cx| {
508 panel.close_session(
509 weak_session_id,
510 window,
511 cx,
512 );
513 })
514 .ok();
515 context_menu
516 .update(cx, |this, cx| {
517 this.cancel(
518 &Default::default(),
519 window,
520 cx,
521 );
522 })
523 .ok();
524 }
525 }),
526 )
527 .into_any_element()
528 })
529 .unwrap_or_else(|_| div().into_any_element())
530 }
531 },
532 {
533 let weak = weak.clone();
534 move |window, cx| {
535 weak.update(cx, |panel, cx| {
536 panel.activate_session(session.clone(), window, cx);
537 })
538 .ok();
539 }
540 },
541 );
542 }
543 this
544 }),
545 )
546 }
547
548 fn deploy_context_menu(
549 &mut self,
550 position: Point<Pixels>,
551 window: &mut Window,
552 cx: &mut Context<Self>,
553 ) {
554 if let Some(running_state) = self
555 .active_session
556 .as_ref()
557 .map(|session| session.read(cx).running_state().clone())
558 {
559 let pane_items_status = running_state.read(cx).pane_items_status(cx);
560 let this = cx.weak_entity();
561
562 let context_menu = ContextMenu::build(window, cx, |mut menu, _window, _cx| {
563 for (item_kind, is_visible) in pane_items_status.into_iter() {
564 menu = menu.toggleable_entry(item_kind, is_visible, IconPosition::End, None, {
565 let this = this.clone();
566 move |window, cx| {
567 this.update(cx, |this, cx| {
568 if let Some(running_state) = this
569 .active_session
570 .as_ref()
571 .map(|session| session.read(cx).running_state().clone())
572 {
573 running_state.update(cx, |state, cx| {
574 if is_visible {
575 state.remove_pane_item(item_kind, window, cx);
576 } else {
577 state.add_pane_item(item_kind, position, window, cx);
578 }
579 })
580 }
581 })
582 .ok();
583 }
584 });
585 }
586
587 menu
588 });
589
590 window.focus(&context_menu.focus_handle(cx));
591 let subscription = cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
592 this.context_menu.take();
593 cx.notify();
594 });
595 self.context_menu = Some((context_menu, position, subscription));
596 }
597 }
598
599 fn top_controls_strip(&self, window: &mut Window, cx: &mut Context<Self>) -> Option<Div> {
600 let active_session = self.active_session.clone();
601 let focus_handle = self.focus_handle.clone();
602
603 Some(
604 h_flex()
605 .border_b_1()
606 .border_color(cx.theme().colors().border)
607 .p_1()
608 .justify_between()
609 .w_full()
610 .child(
611 h_flex().gap_2().w_full().when_some(
612 active_session
613 .as_ref()
614 .map(|session| session.read(cx).running_state()),
615 |this, running_session| {
616 let thread_status = running_session
617 .read(cx)
618 .thread_status(cx)
619 .unwrap_or(project::debugger::session::ThreadStatus::Exited);
620 let capabilities = running_session.read(cx).capabilities(cx);
621 this.map(|this| {
622 if thread_status == ThreadStatus::Running {
623 this.child(
624 IconButton::new("debug-pause", IconName::DebugPause)
625 .icon_size(IconSize::XSmall)
626 .shape(ui::IconButtonShape::Square)
627 .on_click(window.listener_for(
628 &running_session,
629 |this, _, _window, cx| {
630 this.pause_thread(cx);
631 },
632 ))
633 .tooltip({
634 let focus_handle = focus_handle.clone();
635 move |window, cx| {
636 Tooltip::for_action_in(
637 "Pause program",
638 &Pause,
639 &focus_handle,
640 window,
641 cx,
642 )
643 }
644 }),
645 )
646 } else {
647 this.child(
648 IconButton::new("debug-continue", IconName::DebugContinue)
649 .icon_size(IconSize::XSmall)
650 .shape(ui::IconButtonShape::Square)
651 .on_click(window.listener_for(
652 &running_session,
653 |this, _, _window, cx| this.continue_thread(cx),
654 ))
655 .disabled(thread_status != ThreadStatus::Stopped)
656 .tooltip({
657 let focus_handle = focus_handle.clone();
658 move |window, cx| {
659 Tooltip::for_action_in(
660 "Continue program",
661 &Continue,
662 &focus_handle,
663 window,
664 cx,
665 )
666 }
667 }),
668 )
669 }
670 })
671 .child(
672 IconButton::new("debug-step-over", IconName::ArrowRight)
673 .icon_size(IconSize::XSmall)
674 .shape(ui::IconButtonShape::Square)
675 .on_click(window.listener_for(
676 &running_session,
677 |this, _, _window, cx| {
678 this.step_over(cx);
679 },
680 ))
681 .disabled(thread_status != ThreadStatus::Stopped)
682 .tooltip({
683 let focus_handle = focus_handle.clone();
684 move |window, cx| {
685 Tooltip::for_action_in(
686 "Step over",
687 &StepOver,
688 &focus_handle,
689 window,
690 cx,
691 )
692 }
693 }),
694 )
695 .child(
696 IconButton::new("debug-step-out", IconName::ArrowUpRight)
697 .icon_size(IconSize::XSmall)
698 .shape(ui::IconButtonShape::Square)
699 .on_click(window.listener_for(
700 &running_session,
701 |this, _, _window, cx| {
702 this.step_out(cx);
703 },
704 ))
705 .disabled(thread_status != ThreadStatus::Stopped)
706 .tooltip({
707 let focus_handle = focus_handle.clone();
708 move |window, cx| {
709 Tooltip::for_action_in(
710 "Step out",
711 &StepOut,
712 &focus_handle,
713 window,
714 cx,
715 )
716 }
717 }),
718 )
719 .child(
720 IconButton::new("debug-step-into", IconName::ArrowDownRight)
721 .icon_size(IconSize::XSmall)
722 .shape(ui::IconButtonShape::Square)
723 .on_click(window.listener_for(
724 &running_session,
725 |this, _, _window, cx| {
726 this.step_in(cx);
727 },
728 ))
729 .disabled(thread_status != ThreadStatus::Stopped)
730 .tooltip({
731 let focus_handle = focus_handle.clone();
732 move |window, cx| {
733 Tooltip::for_action_in(
734 "Step in",
735 &StepInto,
736 &focus_handle,
737 window,
738 cx,
739 )
740 }
741 }),
742 )
743 .child(Divider::vertical())
744 .child(
745 IconButton::new(
746 "debug-enable-breakpoint",
747 IconName::DebugDisabledBreakpoint,
748 )
749 .icon_size(IconSize::XSmall)
750 .shape(ui::IconButtonShape::Square)
751 .disabled(thread_status != ThreadStatus::Stopped),
752 )
753 .child(
754 IconButton::new("debug-disable-breakpoint", IconName::CircleOff)
755 .icon_size(IconSize::XSmall)
756 .shape(ui::IconButtonShape::Square)
757 .disabled(thread_status != ThreadStatus::Stopped),
758 )
759 .child(
760 IconButton::new("debug-disable-all-breakpoints", IconName::BugOff)
761 .icon_size(IconSize::XSmall)
762 .shape(ui::IconButtonShape::Square)
763 .disabled(
764 thread_status == ThreadStatus::Exited
765 || thread_status == ThreadStatus::Ended,
766 )
767 .on_click(window.listener_for(
768 &running_session,
769 |this, _, _window, cx| {
770 this.toggle_ignore_breakpoints(cx);
771 },
772 ))
773 .tooltip({
774 let focus_handle = focus_handle.clone();
775 move |window, cx| {
776 Tooltip::for_action_in(
777 "Disable all breakpoints",
778 &ToggleIgnoreBreakpoints,
779 &focus_handle,
780 window,
781 cx,
782 )
783 }
784 }),
785 )
786 .child(Divider::vertical())
787 .child(
788 IconButton::new("debug-restart", IconName::DebugRestart)
789 .icon_size(IconSize::XSmall)
790 .on_click(window.listener_for(
791 &running_session,
792 |this, _, _window, cx| {
793 this.restart_session(cx);
794 },
795 ))
796 .tooltip({
797 let focus_handle = focus_handle.clone();
798 move |window, cx| {
799 Tooltip::for_action_in(
800 "Restart",
801 &Restart,
802 &focus_handle,
803 window,
804 cx,
805 )
806 }
807 }),
808 )
809 .child(
810 IconButton::new("debug-stop", IconName::Power)
811 .icon_size(IconSize::XSmall)
812 .on_click(window.listener_for(
813 &running_session,
814 |this, _, _window, cx| {
815 this.stop_thread(cx);
816 },
817 ))
818 .disabled(
819 thread_status != ThreadStatus::Stopped
820 && thread_status != ThreadStatus::Running,
821 )
822 .tooltip({
823 let focus_handle = focus_handle.clone();
824 let label = if capabilities
825 .supports_terminate_threads_request
826 .unwrap_or_default()
827 {
828 "Terminate Thread"
829 } else {
830 "Terminate All Threads"
831 };
832 move |window, cx| {
833 Tooltip::for_action_in(
834 label,
835 &Stop,
836 &focus_handle,
837 window,
838 cx,
839 )
840 }
841 }),
842 )
843 },
844 ),
845 )
846 .child(
847 h_flex()
848 .gap_2()
849 .when_some(
850 active_session
851 .as_ref()
852 .map(|session| session.read(cx).running_state())
853 .cloned(),
854 |this, session| {
855 this.child(
856 session.update(cx, |this, cx| this.thread_dropdown(window, cx)),
857 )
858 .child(Divider::vertical())
859 },
860 )
861 .when_some(active_session.as_ref(), |this, session| {
862 let context_menu = self.sessions_drop_down_menu(session, window, cx);
863 this.child(context_menu).child(Divider::vertical())
864 })
865 .child(
866 IconButton::new("debug-new-session", IconName::Plus)
867 .icon_size(IconSize::Small)
868 .on_click({
869 let workspace = self.workspace.clone();
870 let weak_panel = cx.weak_entity();
871 let past_debug_definition = self.past_debug_definition.clone();
872 move |_, window, cx| {
873 let weak_panel = weak_panel.clone();
874 let past_debug_definition = past_debug_definition.clone();
875
876 let _ = workspace.update(cx, |this, cx| {
877 let workspace = cx.weak_entity();
878 this.toggle_modal(window, cx, |window, cx| {
879 NewSessionModal::new(
880 past_debug_definition,
881 weak_panel,
882 workspace,
883 None,
884 window,
885 cx,
886 )
887 });
888 });
889 }
890 })
891 .tooltip({
892 let focus_handle = focus_handle.clone();
893 move |window, cx| {
894 Tooltip::for_action_in(
895 "New Debug Session",
896 &CreateDebuggingSession,
897 &focus_handle,
898 window,
899 cx,
900 )
901 }
902 }),
903 ),
904 ),
905 )
906 }
907
908 fn activate_pane_in_direction(
909 &mut self,
910 direction: SplitDirection,
911 window: &mut Window,
912 cx: &mut Context<Self>,
913 ) {
914 if let Some(session) = self.active_session() {
915 session.update(cx, |session, cx| {
916 session.running_state().update(cx, |running, cx| {
917 running.activate_pane_in_direction(direction, window, cx);
918 })
919 });
920 }
921 }
922
923 fn activate_item(
924 &mut self,
925 item: DebuggerPaneItem,
926 window: &mut Window,
927 cx: &mut Context<Self>,
928 ) {
929 if let Some(session) = self.active_session() {
930 session.update(cx, |session, cx| {
931 session.running_state().update(cx, |running, cx| {
932 running.activate_item(item, window, cx);
933 });
934 });
935 }
936 }
937
938 fn activate_session(
939 &mut self,
940 session_item: Entity<DebugSession>,
941 window: &mut Window,
942 cx: &mut Context<Self>,
943 ) {
944 debug_assert!(self.sessions.contains(&session_item));
945 session_item.focus_handle(cx).focus(window);
946 session_item.update(cx, |this, cx| {
947 this.running_state().update(cx, |this, cx| {
948 this.go_to_selected_stack_frame(window, cx);
949 });
950 });
951 self.active_session = Some(session_item);
952 cx.notify();
953 }
954}
955
956impl EventEmitter<PanelEvent> for DebugPanel {}
957impl EventEmitter<DebugPanelEvent> for DebugPanel {}
958
959impl Focusable for DebugPanel {
960 fn focus_handle(&self, _: &App) -> FocusHandle {
961 self.focus_handle.clone()
962 }
963}
964
965impl Panel for DebugPanel {
966 fn persistent_name() -> &'static str {
967 "DebugPanel"
968 }
969
970 fn position(&self, _window: &Window, _cx: &App) -> DockPosition {
971 DockPosition::Bottom
972 }
973
974 fn position_is_valid(&self, position: DockPosition) -> bool {
975 position == DockPosition::Bottom
976 }
977
978 fn set_position(
979 &mut self,
980 _position: DockPosition,
981 _window: &mut Window,
982 _cx: &mut Context<Self>,
983 ) {
984 }
985
986 fn size(&self, _window: &Window, _: &App) -> Pixels {
987 self.size
988 }
989
990 fn set_size(&mut self, size: Option<Pixels>, _window: &mut Window, _cx: &mut Context<Self>) {
991 self.size = size.unwrap();
992 }
993
994 fn remote_id() -> Option<proto::PanelId> {
995 Some(proto::PanelId::DebugPanel)
996 }
997
998 fn icon(&self, _window: &Window, _cx: &App) -> Option<IconName> {
999 Some(IconName::Debug)
1000 }
1001
1002 fn icon_tooltip(&self, _window: &Window, cx: &App) -> Option<&'static str> {
1003 if DebuggerSettings::get_global(cx).button {
1004 Some("Debug Panel")
1005 } else {
1006 None
1007 }
1008 }
1009
1010 fn toggle_action(&self) -> Box<dyn Action> {
1011 Box::new(ToggleFocus)
1012 }
1013
1014 fn pane(&self) -> Option<Entity<Pane>> {
1015 None
1016 }
1017
1018 fn activation_priority(&self) -> u32 {
1019 9
1020 }
1021
1022 fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
1023}
1024
1025impl Render for DebugPanel {
1026 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1027 let has_sessions = self.sessions.len() > 0;
1028 let this = cx.weak_entity();
1029 debug_assert_eq!(has_sessions, self.active_session.is_some());
1030
1031 if self
1032 .active_session
1033 .as_ref()
1034 .map(|session| session.read(cx).running_state())
1035 .map(|state| state.read(cx).has_open_context_menu(cx))
1036 .unwrap_or(false)
1037 {
1038 self.context_menu.take();
1039 }
1040
1041 v_flex()
1042 .size_full()
1043 .key_context("DebugPanel")
1044 .child(h_flex().children(self.top_controls_strip(window, cx)))
1045 .track_focus(&self.focus_handle(cx))
1046 .on_action({
1047 let this = this.clone();
1048 move |_: &workspace::ActivatePaneLeft, window, cx| {
1049 this.update(cx, |this, cx| {
1050 this.activate_pane_in_direction(SplitDirection::Left, window, cx);
1051 })
1052 .ok();
1053 }
1054 })
1055 .on_action({
1056 let this = this.clone();
1057 move |_: &workspace::ActivatePaneRight, window, cx| {
1058 this.update(cx, |this, cx| {
1059 this.activate_pane_in_direction(SplitDirection::Right, window, cx);
1060 })
1061 .ok();
1062 }
1063 })
1064 .on_action({
1065 let this = this.clone();
1066 move |_: &workspace::ActivatePaneUp, window, cx| {
1067 this.update(cx, |this, cx| {
1068 this.activate_pane_in_direction(SplitDirection::Up, window, cx);
1069 })
1070 .ok();
1071 }
1072 })
1073 .on_action({
1074 let this = this.clone();
1075 move |_: &workspace::ActivatePaneDown, window, cx| {
1076 this.update(cx, |this, cx| {
1077 this.activate_pane_in_direction(SplitDirection::Down, window, cx);
1078 })
1079 .ok();
1080 }
1081 })
1082 .on_action({
1083 let this = this.clone();
1084 move |_: &FocusConsole, window, cx| {
1085 this.update(cx, |this, cx| {
1086 this.activate_item(DebuggerPaneItem::Console, window, cx);
1087 })
1088 .ok();
1089 }
1090 })
1091 .on_action({
1092 let this = this.clone();
1093 move |_: &FocusVariables, window, cx| {
1094 this.update(cx, |this, cx| {
1095 this.activate_item(DebuggerPaneItem::Variables, window, cx);
1096 })
1097 .ok();
1098 }
1099 })
1100 .on_action({
1101 let this = this.clone();
1102 move |_: &FocusBreakpointList, window, cx| {
1103 this.update(cx, |this, cx| {
1104 this.activate_item(DebuggerPaneItem::BreakpointList, window, cx);
1105 })
1106 .ok();
1107 }
1108 })
1109 .on_action({
1110 let this = this.clone();
1111 move |_: &FocusFrames, window, cx| {
1112 this.update(cx, |this, cx| {
1113 this.activate_item(DebuggerPaneItem::Frames, window, cx);
1114 })
1115 .ok();
1116 }
1117 })
1118 .on_action({
1119 let this = this.clone();
1120 move |_: &FocusModules, window, cx| {
1121 this.update(cx, |this, cx| {
1122 this.activate_item(DebuggerPaneItem::Modules, window, cx);
1123 })
1124 .ok();
1125 }
1126 })
1127 .on_action({
1128 let this = this.clone();
1129 move |_: &FocusLoadedSources, window, cx| {
1130 this.update(cx, |this, cx| {
1131 this.activate_item(DebuggerPaneItem::LoadedSources, window, cx);
1132 })
1133 .ok();
1134 }
1135 })
1136 .on_action({
1137 let this = this.clone();
1138 move |_: &FocusTerminal, window, cx| {
1139 this.update(cx, |this, cx| {
1140 this.activate_item(DebuggerPaneItem::Terminal, window, cx);
1141 })
1142 .ok();
1143 }
1144 })
1145 .when(self.active_session.is_some(), |this| {
1146 this.on_mouse_down(
1147 MouseButton::Right,
1148 cx.listener(|this, event: &MouseDownEvent, window, cx| {
1149 if this
1150 .active_session
1151 .as_ref()
1152 .map(|session| {
1153 let state = session.read(cx).running_state();
1154 state.read(cx).has_pane_at_position(event.position)
1155 })
1156 .unwrap_or(false)
1157 {
1158 this.deploy_context_menu(event.position, window, cx);
1159 }
1160 }),
1161 )
1162 .children(self.context_menu.as_ref().map(|(menu, position, _)| {
1163 deferred(
1164 anchored()
1165 .position(*position)
1166 .anchor(gpui::Corner::TopLeft)
1167 .child(menu.clone()),
1168 )
1169 .with_priority(1)
1170 }))
1171 })
1172 .map(|this| {
1173 if has_sessions {
1174 this.children(self.active_session.clone())
1175 } else {
1176 this.child(
1177 v_flex()
1178 .h_full()
1179 .gap_1()
1180 .items_center()
1181 .justify_center()
1182 .child(
1183 h_flex().child(
1184 Label::new("No Debugging Sessions")
1185 .size(LabelSize::Small)
1186 .color(Color::Muted),
1187 ),
1188 )
1189 .child(
1190 h_flex().flex_shrink().child(
1191 Button::new("spawn-new-session-empty-state", "New Session")
1192 .size(ButtonSize::Large)
1193 .on_click(|_, window, cx| {
1194 window.dispatch_action(
1195 CreateDebuggingSession.boxed_clone(),
1196 cx,
1197 );
1198 }),
1199 ),
1200 ),
1201 )
1202 }
1203 })
1204 .into_any()
1205 }
1206}
1207
1208struct DebuggerProvider(Entity<DebugPanel>);
1209
1210impl workspace::DebuggerProvider for DebuggerProvider {
1211 fn start_session(
1212 &self,
1213 definition: DebugScenario,
1214 context: TaskContext,
1215 buffer: Option<Entity<Buffer>>,
1216 window: &mut Window,
1217 cx: &mut App,
1218 ) {
1219 self.0.update(cx, |_, cx| {
1220 cx.defer_in(window, |this, window, cx| {
1221 this.start_session(definition, context, buffer, window, cx);
1222 })
1223 })
1224 }
1225}