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