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