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