running.rs

  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, &center_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}