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