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