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