1pub(crate) mod breakpoint_list;
2pub(crate) mod console;
3pub(crate) mod loaded_source_list;
4pub(crate) mod module_list;
5pub mod stack_frame_list;
6pub mod variable_list;
7
8use std::{any::Any, ops::ControlFlow, sync::Arc, time::Duration};
9
10use crate::persistence::{self, DebuggerPaneItem, SerializedPaneLayout};
11
12use super::DebugPanelItemEvent;
13use breakpoint_list::BreakpointList;
14use collections::{HashMap, IndexMap};
15use console::Console;
16use dap::{Capabilities, Thread, client::SessionId, debugger_settings::DebuggerSettings};
17use gpui::{
18 Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
19 NoAction, Pixels, Point, Subscription, Task, WeakEntity,
20};
21use loaded_source_list::LoadedSourceList;
22use module_list::ModuleList;
23use project::{
24 Project,
25 debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus},
26};
27use rpc::proto::ViewId;
28use settings::Settings;
29use stack_frame_list::StackFrameList;
30use ui::{
31 ActiveTheme, AnyElement, App, Context, ContextMenu, DropdownMenu, FluentBuilder,
32 InteractiveElement, IntoElement, Label, LabelCommon as _, ParentElement, Render, SharedString,
33 StatefulInteractiveElement, Styled, Tab, Window, div, h_flex, v_flex,
34};
35use util::ResultExt;
36use variable_list::VariableList;
37use workspace::{
38 ActivePaneDecorator, DraggedTab, Item, Member, Pane, PaneGroup, Workspace,
39 item::TabContentParams, move_item, pane::Event,
40};
41
42pub struct RunningState {
43 session: Entity<Session>,
44 thread_id: Option<ThreadId>,
45 focus_handle: FocusHandle,
46 _remote_id: Option<ViewId>,
47 workspace: WeakEntity<Workspace>,
48 session_id: SessionId,
49 variable_list: Entity<variable_list::VariableList>,
50 _subscriptions: Vec<Subscription>,
51 stack_frame_list: Entity<stack_frame_list::StackFrameList>,
52 loaded_sources_list: Entity<LoadedSourceList>,
53 module_list: Entity<module_list::ModuleList>,
54 _console: Entity<Console>,
55 breakpoint_list: Entity<BreakpointList>,
56 panes: PaneGroup,
57 pane_close_subscriptions: HashMap<EntityId, Subscription>,
58 _schedule_serialize: Option<Task<()>>,
59}
60
61impl Render for RunningState {
62 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
63 let active = self.panes.panes().into_iter().next();
64 let x = if let Some(active) = active {
65 self.panes
66 .render(
67 None,
68 &ActivePaneDecorator::new(active, &self.workspace),
69 window,
70 cx,
71 )
72 .into_any_element()
73 } else {
74 div().into_any_element()
75 };
76 let thread_status = self
77 .thread_id
78 .map(|thread_id| self.session.read(cx).thread_status(thread_id))
79 .unwrap_or(ThreadStatus::Exited);
80
81 self.variable_list.update(cx, |this, cx| {
82 this.disabled(thread_status != ThreadStatus::Stopped, cx);
83 });
84 v_flex()
85 .size_full()
86 .key_context("DebugSessionItem")
87 .track_focus(&self.focus_handle(cx))
88 .child(h_flex().flex_1().child(x))
89 }
90}
91
92pub(crate) struct SubView {
93 inner: AnyView,
94 pane_focus_handle: FocusHandle,
95 kind: DebuggerPaneItem,
96 show_indicator: Box<dyn Fn(&App) -> bool>,
97}
98
99impl SubView {
100 pub(crate) fn new(
101 pane_focus_handle: FocusHandle,
102 view: AnyView,
103 kind: DebuggerPaneItem,
104 show_indicator: Option<Box<dyn Fn(&App) -> bool>>,
105 cx: &mut App,
106 ) -> Entity<Self> {
107 cx.new(|_| Self {
108 kind,
109 inner: view,
110 pane_focus_handle,
111 show_indicator: show_indicator.unwrap_or(Box::new(|_| false)),
112 })
113 }
114
115 pub(crate) fn view_kind(&self) -> DebuggerPaneItem {
116 self.kind
117 }
118}
119impl Focusable for SubView {
120 fn focus_handle(&self, _: &App) -> FocusHandle {
121 self.pane_focus_handle.clone()
122 }
123}
124impl EventEmitter<()> for SubView {}
125impl Item for SubView {
126 type Event = ();
127
128 /// This is used to serialize debugger pane layouts
129 /// A SharedString gets converted to a enum and back during serialization/deserialization.
130 fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
131 Some(self.kind.to_shared_string())
132 }
133
134 fn tab_content(
135 &self,
136 params: workspace::item::TabContentParams,
137 _: &Window,
138 cx: &App,
139 ) -> AnyElement {
140 let label = Label::new(self.kind.to_shared_string())
141 .size(ui::LabelSize::Small)
142 .color(params.text_color())
143 .line_height_style(ui::LineHeightStyle::UiLabel);
144
145 if !params.selected && self.show_indicator.as_ref()(cx) {
146 return h_flex()
147 .justify_between()
148 .child(ui::Indicator::dot())
149 .gap_2()
150 .child(label)
151 .into_any_element();
152 }
153
154 label.into_any_element()
155 }
156}
157
158impl Render for SubView {
159 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
160 v_flex().size_full().child(self.inner.clone())
161 }
162}
163
164pub(crate) fn new_debugger_pane(
165 workspace: WeakEntity<Workspace>,
166 project: Entity<Project>,
167 window: &mut Window,
168 cx: &mut Context<RunningState>,
169) -> Entity<Pane> {
170 let weak_running = cx.weak_entity();
171 let custom_drop_handle = {
172 let workspace = workspace.clone();
173 let project = project.downgrade();
174 let weak_running = weak_running.clone();
175 move |pane: &mut Pane, any: &dyn Any, window: &mut Window, cx: &mut Context<Pane>| {
176 let Some(tab) = any.downcast_ref::<DraggedTab>() else {
177 return ControlFlow::Break(());
178 };
179 let Some(project) = project.upgrade() else {
180 return ControlFlow::Break(());
181 };
182 let this_pane = cx.entity().clone();
183 let item = if tab.pane == this_pane {
184 pane.item_for_index(tab.ix)
185 } else {
186 tab.pane.read(cx).item_for_index(tab.ix)
187 };
188 let Some(item) = item.filter(|item| item.downcast::<SubView>().is_some()) else {
189 return ControlFlow::Break(());
190 };
191
192 let source = tab.pane.clone();
193 let item_id_to_move = item.item_id();
194
195 let Ok(new_split_pane) = pane
196 .drag_split_direction()
197 .map(|split_direction| {
198 weak_running.update(cx, |running, cx| {
199 let new_pane =
200 new_debugger_pane(workspace.clone(), project.clone(), window, cx);
201 let _previous_subscription = running.pane_close_subscriptions.insert(
202 new_pane.entity_id(),
203 cx.subscribe_in(&new_pane, window, RunningState::handle_pane_event),
204 );
205 debug_assert!(_previous_subscription.is_none());
206 running
207 .panes
208 .split(&this_pane, &new_pane, split_direction)?;
209 anyhow::Ok(new_pane)
210 })
211 })
212 .transpose()
213 else {
214 return ControlFlow::Break(());
215 };
216
217 match new_split_pane.transpose() {
218 // Source pane may be the one currently updated, so defer the move.
219 Ok(Some(new_pane)) => cx
220 .spawn_in(window, async move |_, cx| {
221 cx.update(|window, cx| {
222 move_item(
223 &source,
224 &new_pane,
225 item_id_to_move,
226 new_pane.read(cx).active_item_index(),
227 window,
228 cx,
229 );
230 })
231 .ok();
232 })
233 .detach(),
234 // If we drop into existing pane or current pane,
235 // regular pane drop handler will take care of it,
236 // using the right tab index for the operation.
237 Ok(None) => return ControlFlow::Continue(()),
238 err @ Err(_) => {
239 err.log_err();
240 return ControlFlow::Break(());
241 }
242 };
243
244 ControlFlow::Break(())
245 }
246 };
247
248 let ret = cx.new(move |cx| {
249 let mut pane = Pane::new(
250 workspace.clone(),
251 project.clone(),
252 Default::default(),
253 None,
254 NoAction.boxed_clone(),
255 window,
256 cx,
257 );
258 pane.set_can_split(Some(Arc::new(move |pane, dragged_item, _window, cx| {
259 if let Some(tab) = dragged_item.downcast_ref::<DraggedTab>() {
260 let is_current_pane = tab.pane == cx.entity();
261 let Some(can_drag_away) = weak_running
262 .update(cx, |running_state, _| {
263 let current_panes = running_state.panes.panes();
264 !current_panes.contains(&&tab.pane)
265 || current_panes.len() > 1
266 || (!is_current_pane || pane.items_len() > 1)
267 })
268 .ok()
269 else {
270 return false;
271 };
272 if can_drag_away {
273 let item = if is_current_pane {
274 pane.item_for_index(tab.ix)
275 } else {
276 tab.pane.read(cx).item_for_index(tab.ix)
277 };
278 if let Some(item) = item {
279 return item.downcast::<SubView>().is_some();
280 }
281 }
282 }
283 false
284 })));
285 pane.display_nav_history_buttons(None);
286 pane.set_custom_drop_handle(cx, custom_drop_handle);
287 pane.set_should_display_tab_bar(|_, _| true);
288 pane.set_render_tab_bar_buttons(cx, |_, _, _| (None, None));
289 pane.set_render_tab_bar(cx, |pane, window, cx| {
290 let active_pane_item = pane.active_item();
291 h_flex()
292 .w_full()
293 .px_2()
294 .gap_1()
295 .h(Tab::container_height(cx))
296 .drag_over::<DraggedTab>(|bar, _, _, cx| {
297 bar.bg(cx.theme().colors().drop_target_background)
298 })
299 .on_drop(
300 cx.listener(move |this, dragged_tab: &DraggedTab, window, cx| {
301 this.drag_split_direction = None;
302 this.handle_tab_drop(dragged_tab, this.items_len(), window, cx)
303 }),
304 )
305 .bg(cx.theme().colors().tab_bar_background)
306 .border_b_1()
307 .border_color(cx.theme().colors().border)
308 .children(pane.items().enumerate().map(|(ix, item)| {
309 let selected = active_pane_item
310 .as_ref()
311 .map_or(false, |active| active.item_id() == item.item_id());
312 let item_ = item.boxed_clone();
313 div()
314 .id(SharedString::from(format!(
315 "debugger_tab_{}",
316 item.item_id().as_u64()
317 )))
318 .p_1()
319 .rounded_md()
320 .cursor_pointer()
321 .map(|this| {
322 if selected {
323 this.bg(cx.theme().colors().tab_active_background)
324 } else {
325 let hover_color = cx.theme().colors().element_hover;
326 this.hover(|style| style.bg(hover_color))
327 }
328 })
329 .on_click(cx.listener(move |this, _, window, cx| {
330 let index = this.index_for_item(&*item_);
331 if let Some(index) = index {
332 this.activate_item(index, true, true, window, cx);
333 }
334 }))
335 .child(item.tab_content(
336 TabContentParams {
337 selected,
338 ..Default::default()
339 },
340 window,
341 cx,
342 ))
343 .on_drop(
344 cx.listener(move |this, dragged_tab: &DraggedTab, window, cx| {
345 this.drag_split_direction = None;
346 this.handle_tab_drop(dragged_tab, ix, window, cx)
347 }),
348 )
349 .on_drag(
350 DraggedTab {
351 item: item.boxed_clone(),
352 pane: cx.entity().clone(),
353 detail: 0,
354 is_active: selected,
355 ix,
356 },
357 |tab, _, _, cx| cx.new(|_| tab.clone()),
358 )
359 }))
360 .into_any_element()
361 });
362 pane
363 });
364
365 ret
366}
367impl RunningState {
368 pub fn new(
369 session: Entity<Session>,
370 project: Entity<Project>,
371 workspace: WeakEntity<Workspace>,
372 serialized_pane_layout: Option<SerializedPaneLayout>,
373 window: &mut Window,
374 cx: &mut Context<Self>,
375 ) -> Self {
376 let focus_handle = cx.focus_handle();
377 let session_id = session.read(cx).session_id();
378 let weak_state = cx.weak_entity();
379 let stack_frame_list = cx.new(|cx| {
380 StackFrameList::new(workspace.clone(), session.clone(), weak_state, window, cx)
381 });
382
383 let variable_list =
384 cx.new(|cx| VariableList::new(session.clone(), stack_frame_list.clone(), window, cx));
385
386 let module_list = cx.new(|cx| ModuleList::new(session.clone(), workspace.clone(), cx));
387
388 let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), cx));
389
390 let console = cx.new(|cx| {
391 Console::new(
392 session.clone(),
393 stack_frame_list.clone(),
394 variable_list.clone(),
395 window,
396 cx,
397 )
398 });
399
400 let breakpoint_list = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
401
402 let _subscriptions = vec![
403 cx.observe(&module_list, |_, _, cx| cx.notify()),
404 cx.subscribe_in(&session, window, |this, _, event, window, cx| {
405 match event {
406 SessionEvent::Stopped(thread_id) => {
407 this.workspace
408 .update(cx, |workspace, cx| {
409 workspace.open_panel::<crate::DebugPanel>(window, cx);
410 })
411 .log_err();
412
413 if let Some(thread_id) = thread_id {
414 this.select_thread(*thread_id, cx);
415 }
416 }
417 SessionEvent::Threads => {
418 let threads = this.session.update(cx, |this, cx| this.threads(cx));
419 this.select_current_thread(&threads, cx);
420 }
421 _ => {}
422 }
423 cx.notify()
424 }),
425 cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
426 this.serialize_layout(window, cx);
427 }),
428 ];
429
430 let mut pane_close_subscriptions = HashMap::default();
431 let panes = if let Some(root) = serialized_pane_layout.and_then(|serialized_layout| {
432 persistence::deserialize_pane_layout(
433 serialized_layout,
434 &workspace,
435 &project,
436 &stack_frame_list,
437 &variable_list,
438 &module_list,
439 &console,
440 &breakpoint_list,
441 &loaded_source_list,
442 &mut pane_close_subscriptions,
443 window,
444 cx,
445 )
446 }) {
447 workspace::PaneGroup::with_root(root)
448 } else {
449 pane_close_subscriptions.clear();
450 let module_list = if session
451 .read(cx)
452 .capabilities()
453 .supports_modules_request
454 .unwrap_or(false)
455 {
456 Some(&module_list)
457 } else {
458 None
459 };
460
461 let loaded_source_list = if session
462 .read(cx)
463 .capabilities()
464 .supports_loaded_sources_request
465 .unwrap_or(false)
466 {
467 Some(&loaded_source_list)
468 } else {
469 None
470 };
471
472 let root = Self::default_pane_layout(
473 project,
474 &workspace,
475 &stack_frame_list,
476 &variable_list,
477 module_list,
478 loaded_source_list,
479 &console,
480 &breakpoint_list,
481 &mut pane_close_subscriptions,
482 window,
483 cx,
484 );
485
486 workspace::PaneGroup::with_root(root)
487 };
488
489 Self {
490 session,
491 workspace,
492 focus_handle,
493 variable_list,
494 _subscriptions,
495 thread_id: None,
496 _remote_id: None,
497 stack_frame_list,
498 session_id,
499 panes,
500 module_list,
501 _console: console,
502 breakpoint_list,
503 loaded_sources_list: loaded_source_list,
504 pane_close_subscriptions,
505 _schedule_serialize: None,
506 }
507 }
508
509 pub(crate) fn remove_pane_item(
510 &mut self,
511 item_kind: DebuggerPaneItem,
512 window: &mut Window,
513 cx: &mut Context<Self>,
514 ) {
515 debug_assert!(
516 item_kind.is_supported(self.session.read(cx).capabilities()),
517 "We should only allow removing supported item kinds"
518 );
519
520 if let Some((pane, item_id)) = self.panes.panes().iter().find_map(|pane| {
521 Some(pane).zip(
522 pane.read(cx)
523 .items()
524 .find(|item| {
525 item.act_as::<SubView>(cx)
526 .is_some_and(|view| view.read(cx).kind == item_kind)
527 })
528 .map(|item| item.item_id()),
529 )
530 }) {
531 pane.update(cx, |pane, cx| {
532 pane.remove_item(item_id, false, true, window, cx)
533 })
534 }
535 }
536
537 pub(crate) fn has_pane_at_position(&self, position: Point<Pixels>) -> bool {
538 self.panes.pane_at_pixel_position(position).is_some()
539 }
540
541 pub(crate) fn add_pane_item(
542 &mut self,
543 item_kind: DebuggerPaneItem,
544 position: Point<Pixels>,
545 window: &mut Window,
546 cx: &mut Context<Self>,
547 ) {
548 debug_assert!(
549 item_kind.is_supported(self.session.read(cx).capabilities()),
550 "We should only allow adding supported item kinds"
551 );
552
553 if let Some(pane) = self.panes.pane_at_pixel_position(position) {
554 let sub_view = match item_kind {
555 DebuggerPaneItem::Console => {
556 let weak_console = self._console.clone().downgrade();
557
558 Box::new(SubView::new(
559 pane.focus_handle(cx),
560 self._console.clone().into(),
561 item_kind,
562 Some(Box::new(move |cx| {
563 weak_console
564 .read_with(cx, |console, cx| console.show_indicator(cx))
565 .unwrap_or_default()
566 })),
567 cx,
568 ))
569 }
570 DebuggerPaneItem::Variables => Box::new(SubView::new(
571 self.variable_list.focus_handle(cx),
572 self.variable_list.clone().into(),
573 item_kind,
574 None,
575 cx,
576 )),
577 DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
578 self.breakpoint_list.focus_handle(cx),
579 self.breakpoint_list.clone().into(),
580 item_kind,
581 None,
582 cx,
583 )),
584 DebuggerPaneItem::Frames => Box::new(SubView::new(
585 self.stack_frame_list.focus_handle(cx),
586 self.stack_frame_list.clone().into(),
587 item_kind,
588 None,
589 cx,
590 )),
591 DebuggerPaneItem::Modules => Box::new(SubView::new(
592 self.module_list.focus_handle(cx),
593 self.module_list.clone().into(),
594 item_kind,
595 None,
596 cx,
597 )),
598 DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
599 self.loaded_sources_list.focus_handle(cx),
600 self.loaded_sources_list.clone().into(),
601 item_kind,
602 None,
603 cx,
604 )),
605 };
606
607 pane.update(cx, |pane, cx| {
608 pane.add_item(sub_view, false, false, None, window, cx);
609 })
610 }
611 }
612
613 pub(crate) fn pane_items_status(&self, cx: &App) -> IndexMap<DebuggerPaneItem, bool> {
614 let caps = self.session.read(cx).capabilities();
615 let mut pane_item_status = IndexMap::from_iter(
616 DebuggerPaneItem::all()
617 .iter()
618 .filter(|kind| kind.is_supported(&caps))
619 .map(|kind| (*kind, false)),
620 );
621 self.panes.panes().iter().for_each(|pane| {
622 pane.read(cx)
623 .items()
624 .filter_map(|item| item.act_as::<SubView>(cx))
625 .for_each(|view| {
626 pane_item_status.insert(view.read(cx).kind, true);
627 });
628 });
629
630 pane_item_status
631 }
632
633 pub(crate) fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
634 if self._schedule_serialize.is_none() {
635 self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
636 cx.background_executor()
637 .timer(Duration::from_millis(100))
638 .await;
639
640 let Some((adapter_name, pane_group)) = this
641 .update(cx, |this, cx| {
642 let adapter_name = this.session.read(cx).adapter_name();
643 (
644 adapter_name,
645 persistence::build_serialized_pane_layout(&this.panes.root, cx),
646 )
647 })
648 .ok()
649 else {
650 return;
651 };
652
653 persistence::serialize_pane_layout(adapter_name, pane_group)
654 .await
655 .log_err();
656
657 this.update(cx, |this, _| {
658 this._schedule_serialize.take();
659 })
660 .ok();
661 }));
662 }
663 }
664
665 pub(crate) fn handle_pane_event(
666 this: &mut RunningState,
667 source_pane: &Entity<Pane>,
668 event: &Event,
669 window: &mut Window,
670 cx: &mut Context<RunningState>,
671 ) {
672 this.serialize_layout(window, cx);
673 if let Event::Remove { .. } = event {
674 let _did_find_pane = this.panes.remove(&source_pane).is_ok();
675 debug_assert!(_did_find_pane);
676 cx.notify();
677 }
678 }
679
680 pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) {
681 if self.thread_id.is_some() {
682 self.stack_frame_list
683 .update(cx, |list, cx| list.go_to_selected_stack_frame(window, cx));
684 }
685 }
686
687 pub(crate) fn has_open_context_menu(&self, cx: &App) -> bool {
688 self.variable_list.read(cx).has_open_context_menu()
689 }
690
691 pub fn session(&self) -> &Entity<Session> {
692 &self.session
693 }
694
695 pub fn session_id(&self) -> SessionId {
696 self.session_id
697 }
698
699 pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option<dap::StackFrameId> {
700 self.stack_frame_list.read(cx).selected_stack_frame_id()
701 }
702
703 #[cfg(test)]
704 pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
705 &self.stack_frame_list
706 }
707
708 #[cfg(test)]
709 pub fn console(&self) -> &Entity<Console> {
710 &self._console
711 }
712
713 #[cfg(test)]
714 pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
715 &self.module_list
716 }
717
718 #[cfg(test)]
719 pub(crate) fn activate_modules_list(&self, window: &mut Window, cx: &mut App) {
720 let (variable_list_position, pane) = self
721 .panes
722 .panes()
723 .into_iter()
724 .find_map(|pane| {
725 pane.read(cx)
726 .items_of_type::<SubView>()
727 .position(|view| view.read(cx).view_kind().to_shared_string() == *"Modules")
728 .map(|view| (view, pane))
729 })
730 .unwrap();
731 pane.update(cx, |this, cx| {
732 this.activate_item(variable_list_position, true, true, window, cx);
733 })
734 }
735 #[cfg(test)]
736 pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
737 &self.variable_list
738 }
739
740 pub fn capabilities(&self, cx: &App) -> Capabilities {
741 self.session().read(cx).capabilities().clone()
742 }
743
744 pub fn select_current_thread(
745 &mut self,
746 threads: &Vec<(Thread, ThreadStatus)>,
747 cx: &mut Context<Self>,
748 ) {
749 let selected_thread = self
750 .thread_id
751 .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0))
752 .or_else(|| threads.first());
753
754 let Some((selected_thread, _)) = selected_thread else {
755 return;
756 };
757
758 if Some(ThreadId(selected_thread.id)) != self.thread_id {
759 self.select_thread(ThreadId(selected_thread.id), cx);
760 }
761 }
762
763 pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
764 self.thread_id
765 }
766
767 pub fn thread_status(&self, cx: &App) -> Option<ThreadStatus> {
768 self.thread_id
769 .map(|id| self.session().read(cx).thread_status(id))
770 }
771
772 fn select_thread(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) {
773 if self.thread_id.is_some_and(|id| id == thread_id) {
774 return;
775 }
776
777 self.thread_id = Some(thread_id);
778
779 self.stack_frame_list
780 .update(cx, |list, cx| list.refresh(cx));
781 cx.notify();
782 }
783
784 pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
785 let Some(thread_id) = self.thread_id else {
786 return;
787 };
788
789 self.session().update(cx, |state, cx| {
790 state.continue_thread(thread_id, cx);
791 });
792 }
793
794 pub fn step_over(&mut self, cx: &mut Context<Self>) {
795 let Some(thread_id) = self.thread_id else {
796 return;
797 };
798
799 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
800
801 self.session().update(cx, |state, cx| {
802 state.step_over(thread_id, granularity, cx);
803 });
804 }
805
806 pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
807 let Some(thread_id) = self.thread_id else {
808 return;
809 };
810
811 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
812
813 self.session().update(cx, |state, cx| {
814 state.step_in(thread_id, granularity, cx);
815 });
816 }
817
818 pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
819 let Some(thread_id) = self.thread_id else {
820 return;
821 };
822
823 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
824
825 self.session().update(cx, |state, cx| {
826 state.step_out(thread_id, granularity, cx);
827 });
828 }
829
830 pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
831 let Some(thread_id) = self.thread_id else {
832 return;
833 };
834
835 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
836
837 self.session().update(cx, |state, cx| {
838 state.step_back(thread_id, granularity, cx);
839 });
840 }
841
842 pub fn restart_session(&self, cx: &mut Context<Self>) {
843 self.session().update(cx, |state, cx| {
844 state.restart(None, cx);
845 });
846 }
847
848 pub fn pause_thread(&self, cx: &mut Context<Self>) {
849 let Some(thread_id) = self.thread_id else {
850 return;
851 };
852
853 self.session().update(cx, |state, cx| {
854 state.pause_thread(thread_id, cx);
855 });
856 }
857
858 pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
859 self.workspace
860 .update(cx, |workspace, cx| {
861 workspace
862 .project()
863 .read(cx)
864 .breakpoint_store()
865 .update(cx, |store, cx| {
866 store.remove_active_position(Some(self.session_id), cx)
867 })
868 })
869 .log_err();
870
871 self.session.update(cx, |session, cx| {
872 session.shutdown(cx).detach();
873 })
874 }
875
876 pub fn stop_thread(&self, cx: &mut Context<Self>) {
877 let Some(thread_id) = self.thread_id else {
878 return;
879 };
880
881 self.workspace
882 .update(cx, |workspace, cx| {
883 workspace
884 .project()
885 .read(cx)
886 .breakpoint_store()
887 .update(cx, |store, cx| {
888 store.remove_active_position(Some(self.session_id), cx)
889 })
890 })
891 .log_err();
892
893 self.session().update(cx, |state, cx| {
894 state.terminate_threads(Some(vec![thread_id; 1]), cx);
895 });
896 }
897
898 #[expect(
899 unused,
900 reason = "Support for disconnecting a client is not wired through yet"
901 )]
902 pub fn disconnect_client(&self, cx: &mut Context<Self>) {
903 self.session().update(cx, |state, cx| {
904 state.disconnect_client(cx);
905 });
906 }
907
908 pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context<Self>) {
909 self.session.update(cx, |session, cx| {
910 session.toggle_ignore_breakpoints(cx).detach();
911 });
912 }
913
914 pub(crate) fn thread_dropdown(
915 &self,
916 window: &mut Window,
917 cx: &mut Context<'_, RunningState>,
918 ) -> DropdownMenu {
919 let state = cx.entity();
920 let threads = self.session.update(cx, |this, cx| this.threads(cx));
921 let selected_thread_name = threads
922 .iter()
923 .find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
924 .map(|(thread, _)| thread.name.clone())
925 .unwrap_or("Threads".to_owned());
926 DropdownMenu::new(
927 ("thread-list", self.session_id.0),
928 selected_thread_name,
929 ContextMenu::build_eager(window, cx, move |mut this, _, _| {
930 for (thread, _) in threads {
931 let state = state.clone();
932 let thread_id = thread.id;
933 this = this.entry(thread.name, None, move |_, cx| {
934 state.update(cx, |state, cx| {
935 state.select_thread(ThreadId(thread_id), cx);
936 });
937 });
938 }
939 this
940 }),
941 )
942 }
943
944 fn default_pane_layout(
945 project: Entity<Project>,
946 workspace: &WeakEntity<Workspace>,
947 stack_frame_list: &Entity<StackFrameList>,
948 variable_list: &Entity<VariableList>,
949 module_list: Option<&Entity<ModuleList>>,
950 loaded_source_list: Option<&Entity<LoadedSourceList>>,
951 console: &Entity<Console>,
952 breakpoints: &Entity<BreakpointList>,
953 subscriptions: &mut HashMap<EntityId, Subscription>,
954 window: &mut Window,
955 cx: &mut Context<'_, RunningState>,
956 ) -> Member {
957 let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
958 leftmost_pane.update(cx, |this, cx| {
959 this.add_item(
960 Box::new(SubView::new(
961 this.focus_handle(cx),
962 stack_frame_list.clone().into(),
963 DebuggerPaneItem::Frames,
964 None,
965 cx,
966 )),
967 true,
968 false,
969 None,
970 window,
971 cx,
972 );
973 this.add_item(
974 Box::new(SubView::new(
975 breakpoints.focus_handle(cx),
976 breakpoints.clone().into(),
977 DebuggerPaneItem::BreakpointList,
978 None,
979 cx,
980 )),
981 true,
982 false,
983 None,
984 window,
985 cx,
986 );
987 this.activate_item(0, false, false, window, cx);
988 });
989 let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
990
991 center_pane.update(cx, |this, cx| {
992 this.add_item(
993 Box::new(SubView::new(
994 variable_list.focus_handle(cx),
995 variable_list.clone().into(),
996 DebuggerPaneItem::Variables,
997 None,
998 cx,
999 )),
1000 true,
1001 false,
1002 None,
1003 window,
1004 cx,
1005 );
1006 if let Some(module_list) = module_list {
1007 this.add_item(
1008 Box::new(SubView::new(
1009 module_list.focus_handle(cx),
1010 module_list.clone().into(),
1011 DebuggerPaneItem::Modules,
1012 None,
1013 cx,
1014 )),
1015 false,
1016 false,
1017 None,
1018 window,
1019 cx,
1020 );
1021 this.activate_item(0, false, false, window, cx);
1022 }
1023
1024 if let Some(loaded_source_list) = loaded_source_list {
1025 this.add_item(
1026 Box::new(SubView::new(
1027 loaded_source_list.focus_handle(cx),
1028 loaded_source_list.clone().into(),
1029 DebuggerPaneItem::LoadedSources,
1030 None,
1031 cx,
1032 )),
1033 false,
1034 false,
1035 None,
1036 window,
1037 cx,
1038 );
1039 this.activate_item(1, false, false, window, cx);
1040 }
1041 });
1042
1043 let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1044 rightmost_pane.update(cx, |this, cx| {
1045 let weak_console = console.downgrade();
1046 this.add_item(
1047 Box::new(SubView::new(
1048 this.focus_handle(cx),
1049 console.clone().into(),
1050 DebuggerPaneItem::Console,
1051 Some(Box::new(move |cx| {
1052 weak_console
1053 .read_with(cx, |console, cx| console.show_indicator(cx))
1054 .unwrap_or_default()
1055 })),
1056 cx,
1057 )),
1058 true,
1059 false,
1060 None,
1061 window,
1062 cx,
1063 );
1064 });
1065
1066 subscriptions.extend(
1067 [&leftmost_pane, ¢er_pane, &rightmost_pane]
1068 .into_iter()
1069 .map(|entity| {
1070 (
1071 entity.entity_id(),
1072 cx.subscribe_in(entity, window, Self::handle_pane_event),
1073 )
1074 }),
1075 );
1076
1077 let group_root = workspace::PaneAxis::new(
1078 gpui::Axis::Horizontal,
1079 [leftmost_pane, center_pane, rightmost_pane]
1080 .into_iter()
1081 .map(workspace::Member::Pane)
1082 .collect(),
1083 );
1084
1085 Member::Axis(group_root)
1086 }
1087}
1088
1089impl EventEmitter<DebugPanelItemEvent> for RunningState {}
1090
1091impl Focusable for RunningState {
1092 fn focus_handle(&self, _: &App) -> FocusHandle {
1093 self.focus_handle.clone()
1094 }
1095}