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 cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
425 this.serialize_layout(window, cx);
426 }),
427 ];
428
429 let mut pane_close_subscriptions = HashMap::default();
430 let panes = if let Some(root) = serialized_pane_layout.and_then(|serialized_layout| {
431 persistence::deserialize_pane_layout(
432 serialized_layout,
433 &workspace,
434 &project,
435 &stack_frame_list,
436 &variable_list,
437 &module_list,
438 &console,
439 &breakpoints,
440 &mut pane_close_subscriptions,
441 window,
442 cx,
443 )
444 }) {
445 workspace::PaneGroup::with_root(root)
446 } else {
447 pane_close_subscriptions.clear();
448 let root = Self::default_pane_layout(
449 project,
450 &workspace,
451 &stack_frame_list,
452 &variable_list,
453 &module_list,
454 &console,
455 breakpoints,
456 &mut pane_close_subscriptions,
457 window,
458 cx,
459 );
460
461 workspace::PaneGroup::with_root(root)
462 };
463
464 Self {
465 session,
466 workspace,
467 focus_handle,
468 variable_list,
469 _subscriptions,
470 thread_id: None,
471 _remote_id: None,
472 stack_frame_list,
473 session_id,
474 panes,
475 _module_list: module_list,
476 _console: console,
477 pane_close_subscriptions,
478 _schedule_serialize: None,
479 }
480 }
481
482 pub(crate) fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
483 if self._schedule_serialize.is_none() {
484 self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
485 cx.background_executor()
486 .timer(Duration::from_millis(100))
487 .await;
488
489 let Some((adapter_name, pane_group)) = this
490 .update(cx, |this, cx| {
491 let adapter_name = this.session.read(cx).adapter_name();
492 (
493 adapter_name,
494 persistence::build_serialized_pane_layout(&this.panes.root, cx),
495 )
496 })
497 .ok()
498 else {
499 return;
500 };
501
502 persistence::serialize_pane_layout(adapter_name, pane_group)
503 .await
504 .log_err();
505
506 this.update(cx, |this, _| {
507 this._schedule_serialize.take();
508 })
509 .ok();
510 }));
511 }
512 }
513
514 pub(crate) fn handle_pane_event(
515 this: &mut RunningState,
516 source_pane: &Entity<Pane>,
517 event: &Event,
518 window: &mut Window,
519 cx: &mut Context<RunningState>,
520 ) {
521 this.serialize_layout(window, cx);
522 if let Event::Remove { .. } = event {
523 let _did_find_pane = this.panes.remove(&source_pane).is_ok();
524 debug_assert!(_did_find_pane);
525 cx.notify();
526 }
527 }
528
529 pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) {
530 if self.thread_id.is_some() {
531 self.stack_frame_list
532 .update(cx, |list, cx| list.go_to_selected_stack_frame(window, cx));
533 }
534 }
535
536 pub fn session(&self) -> &Entity<Session> {
537 &self.session
538 }
539
540 pub fn session_id(&self) -> SessionId {
541 self.session_id
542 }
543
544 pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option<dap::StackFrameId> {
545 self.stack_frame_list.read(cx).selected_stack_frame_id()
546 }
547
548 #[cfg(test)]
549 pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
550 &self.stack_frame_list
551 }
552
553 #[cfg(test)]
554 pub fn console(&self) -> &Entity<Console> {
555 &self._console
556 }
557
558 #[cfg(test)]
559 pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
560 &self._module_list
561 }
562
563 #[cfg(test)]
564 pub(crate) fn activate_modules_list(&self, window: &mut Window, cx: &mut App) {
565 let (variable_list_position, pane) = self
566 .panes
567 .panes()
568 .into_iter()
569 .find_map(|pane| {
570 pane.read(cx)
571 .items_of_type::<SubView>()
572 .position(|view| view.read(cx).view_kind().to_shared_string() == *"Modules")
573 .map(|view| (view, pane))
574 })
575 .unwrap();
576 pane.update(cx, |this, cx| {
577 this.activate_item(variable_list_position, true, true, window, cx);
578 })
579 }
580 #[cfg(test)]
581 pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
582 &self.variable_list
583 }
584
585 pub fn capabilities(&self, cx: &App) -> Capabilities {
586 self.session().read(cx).capabilities().clone()
587 }
588
589 pub fn select_current_thread(
590 &mut self,
591 threads: &Vec<(Thread, ThreadStatus)>,
592 cx: &mut Context<Self>,
593 ) {
594 let selected_thread = self
595 .thread_id
596 .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0))
597 .or_else(|| threads.first());
598
599 let Some((selected_thread, _)) = selected_thread else {
600 return;
601 };
602
603 if Some(ThreadId(selected_thread.id)) != self.thread_id {
604 self.select_thread(ThreadId(selected_thread.id), cx);
605 }
606 }
607
608 pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
609 self.thread_id
610 }
611
612 pub fn thread_status(&self, cx: &App) -> Option<ThreadStatus> {
613 self.thread_id
614 .map(|id| self.session().read(cx).thread_status(id))
615 }
616
617 fn select_thread(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) {
618 if self.thread_id.is_some_and(|id| id == thread_id) {
619 return;
620 }
621
622 self.thread_id = Some(thread_id);
623
624 self.stack_frame_list
625 .update(cx, |list, cx| list.refresh(cx));
626 cx.notify();
627 }
628
629 pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
630 let Some(thread_id) = self.thread_id else {
631 return;
632 };
633
634 self.session().update(cx, |state, cx| {
635 state.continue_thread(thread_id, cx);
636 });
637 }
638
639 pub fn step_over(&mut self, cx: &mut Context<Self>) {
640 let Some(thread_id) = self.thread_id else {
641 return;
642 };
643
644 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
645
646 self.session().update(cx, |state, cx| {
647 state.step_over(thread_id, granularity, cx);
648 });
649 }
650
651 pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
652 let Some(thread_id) = self.thread_id else {
653 return;
654 };
655
656 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
657
658 self.session().update(cx, |state, cx| {
659 state.step_in(thread_id, granularity, cx);
660 });
661 }
662
663 pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
664 let Some(thread_id) = self.thread_id else {
665 return;
666 };
667
668 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
669
670 self.session().update(cx, |state, cx| {
671 state.step_out(thread_id, granularity, cx);
672 });
673 }
674
675 pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
676 let Some(thread_id) = self.thread_id else {
677 return;
678 };
679
680 let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
681
682 self.session().update(cx, |state, cx| {
683 state.step_back(thread_id, granularity, cx);
684 });
685 }
686
687 pub fn restart_session(&self, cx: &mut Context<Self>) {
688 self.session().update(cx, |state, cx| {
689 state.restart(None, cx);
690 });
691 }
692
693 pub fn pause_thread(&self, cx: &mut Context<Self>) {
694 let Some(thread_id) = self.thread_id else {
695 return;
696 };
697
698 self.session().update(cx, |state, cx| {
699 state.pause_thread(thread_id, cx);
700 });
701 }
702
703 pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
704 self.workspace
705 .update(cx, |workspace, cx| {
706 workspace
707 .project()
708 .read(cx)
709 .breakpoint_store()
710 .update(cx, |store, cx| {
711 store.remove_active_position(Some(self.session_id), cx)
712 })
713 })
714 .log_err();
715
716 self.session.update(cx, |session, cx| {
717 session.shutdown(cx).detach();
718 })
719 }
720
721 pub fn stop_thread(&self, cx: &mut Context<Self>) {
722 let Some(thread_id) = self.thread_id else {
723 return;
724 };
725
726 self.workspace
727 .update(cx, |workspace, cx| {
728 workspace
729 .project()
730 .read(cx)
731 .breakpoint_store()
732 .update(cx, |store, cx| {
733 store.remove_active_position(Some(self.session_id), cx)
734 })
735 })
736 .log_err();
737
738 self.session().update(cx, |state, cx| {
739 state.terminate_threads(Some(vec![thread_id; 1]), cx);
740 });
741 }
742
743 #[expect(
744 unused,
745 reason = "Support for disconnecting a client is not wired through yet"
746 )]
747 pub fn disconnect_client(&self, cx: &mut Context<Self>) {
748 self.session().update(cx, |state, cx| {
749 state.disconnect_client(cx);
750 });
751 }
752
753 pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context<Self>) {
754 self.session.update(cx, |session, cx| {
755 session.toggle_ignore_breakpoints(cx).detach();
756 });
757 }
758
759 pub(crate) fn thread_dropdown(
760 &self,
761 window: &mut Window,
762 cx: &mut Context<'_, RunningState>,
763 ) -> DropdownMenu {
764 let state = cx.entity();
765 let threads = self.session.update(cx, |this, cx| this.threads(cx));
766 let selected_thread_name = threads
767 .iter()
768 .find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
769 .map(|(thread, _)| thread.name.clone())
770 .unwrap_or("Threads".to_owned());
771 DropdownMenu::new(
772 ("thread-list", self.session_id.0),
773 selected_thread_name,
774 ContextMenu::build_eager(window, cx, move |mut this, _, _| {
775 for (thread, _) in threads {
776 let state = state.clone();
777 let thread_id = thread.id;
778 this = this.entry(thread.name, None, move |_, cx| {
779 state.update(cx, |state, cx| {
780 state.select_thread(ThreadId(thread_id), cx);
781 });
782 });
783 }
784 this
785 }),
786 )
787 }
788
789 fn default_pane_layout(
790 project: Entity<Project>,
791 workspace: &WeakEntity<Workspace>,
792 stack_frame_list: &Entity<StackFrameList>,
793 variable_list: &Entity<VariableList>,
794 module_list: &Entity<ModuleList>,
795 console: &Entity<Console>,
796 breakpoints: Entity<BreakpointList>,
797 subscriptions: &mut HashMap<EntityId, Subscription>,
798 window: &mut Window,
799 cx: &mut Context<'_, RunningState>,
800 ) -> Member {
801 let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
802 leftmost_pane.update(cx, |this, cx| {
803 this.add_item(
804 Box::new(SubView::new(
805 this.focus_handle(cx),
806 stack_frame_list.clone().into(),
807 DebuggerPaneItem::Frames,
808 None,
809 cx,
810 )),
811 true,
812 false,
813 None,
814 window,
815 cx,
816 );
817 this.add_item(
818 Box::new(SubView::new(
819 breakpoints.focus_handle(cx),
820 breakpoints.into(),
821 DebuggerPaneItem::BreakpointList,
822 None,
823 cx,
824 )),
825 true,
826 false,
827 None,
828 window,
829 cx,
830 );
831 this.activate_item(0, false, false, window, cx);
832 });
833 let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
834 center_pane.update(cx, |this, cx| {
835 this.add_item(
836 Box::new(SubView::new(
837 variable_list.focus_handle(cx),
838 variable_list.clone().into(),
839 DebuggerPaneItem::Variables,
840 None,
841 cx,
842 )),
843 true,
844 false,
845 None,
846 window,
847 cx,
848 );
849 this.add_item(
850 Box::new(SubView::new(
851 this.focus_handle(cx),
852 module_list.clone().into(),
853 DebuggerPaneItem::Modules,
854 None,
855 cx,
856 )),
857 false,
858 false,
859 None,
860 window,
861 cx,
862 );
863 this.activate_item(0, false, false, window, cx);
864 });
865 let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
866 rightmost_pane.update(cx, |this, cx| {
867 let weak_console = console.downgrade();
868 this.add_item(
869 Box::new(SubView::new(
870 this.focus_handle(cx),
871 console.clone().into(),
872 DebuggerPaneItem::Console,
873 Some(Box::new(move |cx| {
874 weak_console
875 .read_with(cx, |console, cx| console.show_indicator(cx))
876 .unwrap_or_default()
877 })),
878 cx,
879 )),
880 true,
881 false,
882 None,
883 window,
884 cx,
885 );
886 });
887
888 subscriptions.extend(
889 [&leftmost_pane, ¢er_pane, &rightmost_pane]
890 .into_iter()
891 .map(|entity| {
892 (
893 entity.entity_id(),
894 cx.subscribe_in(entity, window, Self::handle_pane_event),
895 )
896 }),
897 );
898
899 let group_root = workspace::PaneAxis::new(
900 gpui::Axis::Horizontal,
901 [leftmost_pane, center_pane, rightmost_pane]
902 .into_iter()
903 .map(workspace::Member::Pane)
904 .collect(),
905 );
906
907 Member::Axis(group_root)
908 }
909}
910
911impl EventEmitter<DebugPanelItemEvent> for RunningState {}
912
913impl Focusable for RunningState {
914 fn focus_handle(&self, _: &App) -> FocusHandle {
915 self.focus_handle.clone()
916 }
917}