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, path::PathBuf, sync::Arc, time::Duration};
   9
  10use crate::persistence::{self, DebuggerPaneItem, SerializedLayout};
  11
  12use super::DebugPanelItemEvent;
  13use anyhow::{Result, anyhow};
  14use breakpoint_list::BreakpointList;
  15use collections::{HashMap, IndexMap};
  16use console::Console;
  17use dap::{
  18    Capabilities, RunInTerminalRequestArguments, Thread,
  19    adapters::{DebugAdapterName, DebugTaskDefinition},
  20    client::SessionId,
  21    debugger_settings::DebuggerSettings,
  22};
  23use futures::{SinkExt, channel::mpsc};
  24use gpui::{
  25    Action as _, AnyView, AppContext, Axis, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
  26    NoAction, Pixels, Point, Subscription, Task, WeakEntity,
  27};
  28use language::Buffer;
  29use loaded_source_list::LoadedSourceList;
  30use module_list::ModuleList;
  31use project::{
  32    Project,
  33    debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus},
  34    terminals::TerminalKind,
  35};
  36use rpc::proto::ViewId;
  37use serde_json::Value;
  38use settings::Settings;
  39use stack_frame_list::StackFrameList;
  40use task::{
  41    DebugScenario, LaunchRequest, TaskContext, substitute_variables_in_map,
  42    substitute_variables_in_str,
  43};
  44use terminal_view::TerminalView;
  45use ui::{
  46    ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, ContextMenu,
  47    DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, InteractiveElement, IntoElement,
  48    Label, LabelCommon as _, ParentElement, Render, SharedString, StatefulInteractiveElement,
  49    Styled, Tab, Tooltip, VisibleOnHover, VisualContext, Window, div, h_flex, v_flex,
  50};
  51use util::ResultExt;
  52use variable_list::VariableList;
  53use workspace::{
  54    ActivePaneDecorator, DraggedTab, Item, ItemHandle, Member, Pane, PaneGroup, SplitDirection,
  55    Workspace, item::TabContentParams, move_item, pane::Event,
  56};
  57
  58pub struct RunningState {
  59    session: Entity<Session>,
  60    thread_id: Option<ThreadId>,
  61    focus_handle: FocusHandle,
  62    _remote_id: Option<ViewId>,
  63    workspace: WeakEntity<Workspace>,
  64    session_id: SessionId,
  65    variable_list: Entity<variable_list::VariableList>,
  66    _subscriptions: Vec<Subscription>,
  67    stack_frame_list: Entity<stack_frame_list::StackFrameList>,
  68    loaded_sources_list: Entity<LoadedSourceList>,
  69    pub debug_terminal: Entity<DebugTerminal>,
  70    module_list: Entity<module_list::ModuleList>,
  71    console: Entity<Console>,
  72    breakpoint_list: Entity<BreakpointList>,
  73    panes: PaneGroup,
  74    active_pane: Option<Entity<Pane>>,
  75    pane_close_subscriptions: HashMap<EntityId, Subscription>,
  76    dock_axis: Axis,
  77    _schedule_serialize: Option<Task<()>>,
  78}
  79
  80impl Render for RunningState {
  81    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
  82        let zoomed_pane = self
  83            .panes
  84            .panes()
  85            .into_iter()
  86            .find(|pane| pane.read(cx).is_zoomed());
  87
  88        let active = self.panes.panes().into_iter().next();
  89        let x = if let Some(ref zoomed_pane) = zoomed_pane {
  90            zoomed_pane.update(cx, |pane, cx| pane.render(window, cx).into_any_element())
  91        } else if let Some(active) = active {
  92            self.panes
  93                .render(
  94                    None,
  95                    &ActivePaneDecorator::new(active, &self.workspace),
  96                    window,
  97                    cx,
  98                )
  99                .into_any_element()
 100        } else {
 101            div().into_any_element()
 102        };
 103        let thread_status = self
 104            .thread_id
 105            .map(|thread_id| self.session.read(cx).thread_status(thread_id))
 106            .unwrap_or(ThreadStatus::Exited);
 107
 108        self.variable_list.update(cx, |this, cx| {
 109            this.disabled(thread_status != ThreadStatus::Stopped, cx);
 110        });
 111        v_flex()
 112            .size_full()
 113            .key_context("DebugSessionItem")
 114            .track_focus(&self.focus_handle(cx))
 115            .child(h_flex().flex_1().child(x))
 116    }
 117}
 118
 119pub(crate) struct SubView {
 120    inner: AnyView,
 121    pane_focus_handle: FocusHandle,
 122    kind: DebuggerPaneItem,
 123    show_indicator: Box<dyn Fn(&App) -> bool>,
 124    hovered: bool,
 125}
 126
 127impl SubView {
 128    pub(crate) fn new(
 129        pane_focus_handle: FocusHandle,
 130        view: AnyView,
 131        kind: DebuggerPaneItem,
 132        show_indicator: Option<Box<dyn Fn(&App) -> bool>>,
 133        cx: &mut App,
 134    ) -> Entity<Self> {
 135        cx.new(|_| Self {
 136            kind,
 137            inner: view,
 138            pane_focus_handle,
 139            show_indicator: show_indicator.unwrap_or(Box::new(|_| false)),
 140            hovered: false,
 141        })
 142    }
 143
 144    pub(crate) fn view_kind(&self) -> DebuggerPaneItem {
 145        self.kind
 146    }
 147}
 148impl Focusable for SubView {
 149    fn focus_handle(&self, _: &App) -> FocusHandle {
 150        self.pane_focus_handle.clone()
 151    }
 152}
 153impl EventEmitter<()> for SubView {}
 154impl Item for SubView {
 155    type Event = ();
 156
 157    /// This is used to serialize debugger pane layouts
 158    /// A SharedString gets converted to a enum and back during serialization/deserialization.
 159    fn tab_content_text(&self, _detail: usize, _cx: &App) -> SharedString {
 160        self.kind.to_shared_string()
 161    }
 162
 163    fn tab_content(
 164        &self,
 165        params: workspace::item::TabContentParams,
 166        _: &Window,
 167        cx: &App,
 168    ) -> AnyElement {
 169        let label = Label::new(self.kind.to_shared_string())
 170            .size(ui::LabelSize::Small)
 171            .color(params.text_color())
 172            .line_height_style(ui::LineHeightStyle::UiLabel);
 173
 174        if !params.selected && self.show_indicator.as_ref()(cx) {
 175            return h_flex()
 176                .justify_between()
 177                .child(ui::Indicator::dot())
 178                .gap_2()
 179                .child(label)
 180                .into_any_element();
 181        }
 182
 183        label.into_any_element()
 184    }
 185}
 186
 187impl Render for SubView {
 188    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
 189        v_flex()
 190            .id(SharedString::from(format!(
 191                "subview-container-{}",
 192                self.kind.to_shared_string()
 193            )))
 194            .on_hover(cx.listener(|this, hovered, _, cx| {
 195                this.hovered = *hovered;
 196                cx.notify();
 197            }))
 198            .size_full()
 199            // Add border unconditionally to prevent layout shifts on focus changes.
 200            .border_1()
 201            .when(self.pane_focus_handle.contains_focused(window, cx), |el| {
 202                el.border_color(cx.theme().colors().pane_focused_border)
 203            })
 204            .child(self.inner.clone())
 205    }
 206}
 207
 208pub(crate) fn new_debugger_pane(
 209    workspace: WeakEntity<Workspace>,
 210    project: Entity<Project>,
 211    window: &mut Window,
 212    cx: &mut Context<RunningState>,
 213) -> Entity<Pane> {
 214    let weak_running = cx.weak_entity();
 215    let custom_drop_handle = {
 216        let workspace = workspace.clone();
 217        let project = project.downgrade();
 218        let weak_running = weak_running.clone();
 219        move |pane: &mut Pane, any: &dyn Any, window: &mut Window, cx: &mut Context<Pane>| {
 220            let Some(tab) = any.downcast_ref::<DraggedTab>() else {
 221                return ControlFlow::Break(());
 222            };
 223            let Some(project) = project.upgrade() else {
 224                return ControlFlow::Break(());
 225            };
 226            let this_pane = cx.entity().clone();
 227            let item = if tab.pane == this_pane {
 228                pane.item_for_index(tab.ix)
 229            } else {
 230                tab.pane.read(cx).item_for_index(tab.ix)
 231            };
 232            let Some(item) = item.filter(|item| item.downcast::<SubView>().is_some()) else {
 233                return ControlFlow::Break(());
 234            };
 235
 236            let source = tab.pane.clone();
 237            let item_id_to_move = item.item_id();
 238
 239            let Ok(new_split_pane) = pane
 240                .drag_split_direction()
 241                .map(|split_direction| {
 242                    weak_running.update(cx, |running, cx| {
 243                        let new_pane =
 244                            new_debugger_pane(workspace.clone(), project.clone(), window, cx);
 245                        let _previous_subscription = running.pane_close_subscriptions.insert(
 246                            new_pane.entity_id(),
 247                            cx.subscribe_in(&new_pane, window, RunningState::handle_pane_event),
 248                        );
 249                        debug_assert!(_previous_subscription.is_none());
 250                        running
 251                            .panes
 252                            .split(&this_pane, &new_pane, split_direction)?;
 253                        anyhow::Ok(new_pane)
 254                    })
 255                })
 256                .transpose()
 257            else {
 258                return ControlFlow::Break(());
 259            };
 260
 261            match new_split_pane.transpose() {
 262                // Source pane may be the one currently updated, so defer the move.
 263                Ok(Some(new_pane)) => cx
 264                    .spawn_in(window, async move |_, cx| {
 265                        cx.update(|window, cx| {
 266                            move_item(
 267                                &source,
 268                                &new_pane,
 269                                item_id_to_move,
 270                                new_pane.read(cx).active_item_index(),
 271                                window,
 272                                cx,
 273                            );
 274                        })
 275                        .ok();
 276                    })
 277                    .detach(),
 278                // If we drop into existing pane or current pane,
 279                // regular pane drop handler will take care of it,
 280                // using the right tab index for the operation.
 281                Ok(None) => return ControlFlow::Continue(()),
 282                err @ Err(_) => {
 283                    err.log_err();
 284                    return ControlFlow::Break(());
 285                }
 286            };
 287
 288            ControlFlow::Break(())
 289        }
 290    };
 291
 292    let ret = cx.new(move |cx| {
 293        let mut pane = Pane::new(
 294            workspace.clone(),
 295            project.clone(),
 296            Default::default(),
 297            None,
 298            NoAction.boxed_clone(),
 299            window,
 300            cx,
 301        );
 302        let focus_handle = pane.focus_handle(cx);
 303        pane.set_can_split(Some(Arc::new({
 304            let weak_running = weak_running.clone();
 305            move |pane, dragged_item, _window, cx| {
 306                if let Some(tab) = dragged_item.downcast_ref::<DraggedTab>() {
 307                    let is_current_pane = tab.pane == cx.entity();
 308                    let Some(can_drag_away) = weak_running
 309                        .update(cx, |running_state, _| {
 310                            let current_panes = running_state.panes.panes();
 311                            !current_panes.contains(&&tab.pane)
 312                                || current_panes.len() > 1
 313                                || (!is_current_pane || pane.items_len() > 1)
 314                        })
 315                        .ok()
 316                    else {
 317                        return false;
 318                    };
 319                    if can_drag_away {
 320                        let item = if is_current_pane {
 321                            pane.item_for_index(tab.ix)
 322                        } else {
 323                            tab.pane.read(cx).item_for_index(tab.ix)
 324                        };
 325                        if let Some(item) = item {
 326                            return item.downcast::<SubView>().is_some();
 327                        }
 328                    }
 329                }
 330                false
 331            }
 332        })));
 333        pane.display_nav_history_buttons(None);
 334        pane.set_custom_drop_handle(cx, custom_drop_handle);
 335        pane.set_should_display_tab_bar(|_, _| true);
 336        pane.set_render_tab_bar_buttons(cx, |_, _, _| (None, None));
 337        pane.set_render_tab_bar(cx, {
 338            move |pane, window, cx| {
 339                let active_pane_item = pane.active_item();
 340                let pane_group_id: SharedString =
 341                    format!("pane-zoom-button-hover-{}", cx.entity_id()).into();
 342                let is_hovered = active_pane_item.as_ref().map_or(false, |item| {
 343                    item.downcast::<SubView>()
 344                        .map_or(false, |this| this.read(cx).hovered)
 345                });
 346                h_flex()
 347                    .group(pane_group_id.clone())
 348                    .justify_between()
 349                    .bg(cx.theme().colors().tab_bar_background)
 350                    .border_b_1()
 351                    .px_2()
 352                    .border_color(cx.theme().colors().border)
 353                    .track_focus(&focus_handle)
 354                    .child(
 355                        h_flex()
 356                            .w_full()
 357                            .gap_1()
 358                            .h(Tab::container_height(cx))
 359                            .drag_over::<DraggedTab>(|bar, _, _, cx| {
 360                                bar.bg(cx.theme().colors().drop_target_background)
 361                            })
 362                            .on_drop(cx.listener(
 363                                move |this, dragged_tab: &DraggedTab, window, cx| {
 364                                    this.drag_split_direction = None;
 365                                    this.handle_tab_drop(dragged_tab, this.items_len(), window, cx)
 366                                },
 367                            ))
 368                            .children(pane.items().enumerate().map(|(ix, item)| {
 369                                let selected = active_pane_item
 370                                    .as_ref()
 371                                    .map_or(false, |active| active.item_id() == item.item_id());
 372                                let deemphasized = !pane.has_focus(window, cx);
 373                                let item_ = item.boxed_clone();
 374                                div()
 375                                    .id(SharedString::from(format!(
 376                                        "debugger_tab_{}",
 377                                        item.item_id().as_u64()
 378                                    )))
 379                                    .p_1()
 380                                    .rounded_md()
 381                                    .cursor_pointer()
 382                                    .map(|this| {
 383                                        let theme = cx.theme();
 384                                        if selected {
 385                                            let color = theme.colors().tab_active_background;
 386                                            let color = if deemphasized {
 387                                                color.opacity(0.5)
 388                                            } else {
 389                                                color
 390                                            };
 391                                            this.bg(color)
 392                                        } else {
 393                                            let hover_color = theme.colors().element_hover;
 394                                            this.hover(|style| style.bg(hover_color))
 395                                        }
 396                                    })
 397                                    .on_click(cx.listener(move |this, _, window, cx| {
 398                                        let index = this.index_for_item(&*item_);
 399                                        if let Some(index) = index {
 400                                            this.activate_item(index, true, true, window, cx);
 401                                        }
 402                                    }))
 403                                    .child(item.tab_content(
 404                                        TabContentParams {
 405                                            selected,
 406                                            deemphasized,
 407                                            ..Default::default()
 408                                        },
 409                                        window,
 410                                        cx,
 411                                    ))
 412                                    .on_drop(cx.listener(
 413                                        move |this, dragged_tab: &DraggedTab, window, cx| {
 414                                            this.drag_split_direction = None;
 415                                            this.handle_tab_drop(dragged_tab, ix, window, cx)
 416                                        },
 417                                    ))
 418                                    .on_drag(
 419                                        DraggedTab {
 420                                            item: item.boxed_clone(),
 421                                            pane: cx.entity().clone(),
 422                                            detail: 0,
 423                                            is_active: selected,
 424                                            ix,
 425                                        },
 426                                        |tab, _, _, cx| cx.new(|_| tab.clone()),
 427                                    )
 428                            })),
 429                    )
 430                    .child({
 431                        let zoomed = pane.is_zoomed();
 432                        div()
 433                            .visible_on_hover(pane_group_id)
 434                            .when(is_hovered, |this| this.visible())
 435                            .child(
 436                                IconButton::new(
 437                                    SharedString::from(format!(
 438                                        "debug-toggle-zoom-{}",
 439                                        cx.entity_id()
 440                                    )),
 441                                    if zoomed {
 442                                        IconName::Minimize
 443                                    } else {
 444                                        IconName::Maximize
 445                                    },
 446                                )
 447                                .icon_size(IconSize::XSmall)
 448                                .on_click(cx.listener(move |pane, _, window, cx| {
 449                                    pane.toggle_zoom(&workspace::ToggleZoom, window, cx);
 450                                }))
 451                                .tooltip({
 452                                    let focus_handle = focus_handle.clone();
 453                                    move |window, cx| {
 454                                        let zoomed_text =
 455                                            if zoomed { "Zoom Out" } else { "Zoom In" };
 456                                        Tooltip::for_action_in(
 457                                            zoomed_text,
 458                                            &workspace::ToggleZoom,
 459                                            &focus_handle,
 460                                            window,
 461                                            cx,
 462                                        )
 463                                    }
 464                                }),
 465                            )
 466                    })
 467                    .into_any_element()
 468            }
 469        });
 470        pane
 471    });
 472
 473    ret
 474}
 475
 476pub struct DebugTerminal {
 477    pub terminal: Option<Entity<TerminalView>>,
 478    focus_handle: FocusHandle,
 479}
 480
 481impl DebugTerminal {
 482    fn empty(cx: &mut Context<Self>) -> Self {
 483        Self {
 484            terminal: None,
 485            focus_handle: cx.focus_handle(),
 486        }
 487    }
 488}
 489
 490impl gpui::Render for DebugTerminal {
 491    fn render(&mut self, _window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
 492        if let Some(terminal) = self.terminal.clone() {
 493            terminal.into_any_element()
 494        } else {
 495            div().track_focus(&self.focus_handle).into_any_element()
 496        }
 497    }
 498}
 499impl Focusable for DebugTerminal {
 500    fn focus_handle(&self, cx: &App) -> FocusHandle {
 501        if let Some(terminal) = self.terminal.as_ref() {
 502            return terminal.focus_handle(cx);
 503        } else {
 504            self.focus_handle.clone()
 505        }
 506    }
 507}
 508
 509impl RunningState {
 510    pub fn new(
 511        session: Entity<Session>,
 512        project: Entity<Project>,
 513        workspace: WeakEntity<Workspace>,
 514        serialized_pane_layout: Option<SerializedLayout>,
 515        dock_axis: Axis,
 516        window: &mut Window,
 517        cx: &mut Context<Self>,
 518    ) -> Self {
 519        let focus_handle = cx.focus_handle();
 520        let session_id = session.read(cx).session_id();
 521        let weak_state = cx.weak_entity();
 522        let stack_frame_list = cx.new(|cx| {
 523            StackFrameList::new(workspace.clone(), session.clone(), weak_state, window, cx)
 524        });
 525
 526        let debug_terminal = cx.new(DebugTerminal::empty);
 527
 528        let variable_list =
 529            cx.new(|cx| VariableList::new(session.clone(), stack_frame_list.clone(), window, cx));
 530
 531        let module_list = cx.new(|cx| ModuleList::new(session.clone(), workspace.clone(), cx));
 532
 533        let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), cx));
 534
 535        let console = cx.new(|cx| {
 536            Console::new(
 537                session.clone(),
 538                stack_frame_list.clone(),
 539                variable_list.clone(),
 540                window,
 541                cx,
 542            )
 543        });
 544
 545        let breakpoint_list = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
 546
 547        let _subscriptions = vec![
 548            cx.observe(&module_list, |_, _, cx| cx.notify()),
 549            cx.subscribe_in(&session, window, |this, _, event, window, cx| {
 550                match event {
 551                    SessionEvent::Stopped(thread_id) => {
 552                        this.workspace
 553                            .update(cx, |workspace, cx| {
 554                                workspace.open_panel::<crate::DebugPanel>(window, cx);
 555                            })
 556                            .log_err();
 557
 558                        if let Some(thread_id) = thread_id {
 559                            this.select_thread(*thread_id, window, cx);
 560                        }
 561                    }
 562                    SessionEvent::Threads => {
 563                        let threads = this.session.update(cx, |this, cx| this.threads(cx));
 564                        this.select_current_thread(&threads, window, cx);
 565                    }
 566                    SessionEvent::CapabilitiesLoaded => {
 567                        let capabilities = this.capabilities(cx);
 568                        if !capabilities.supports_modules_request.unwrap_or(false) {
 569                            this.remove_pane_item(DebuggerPaneItem::Modules, window, cx);
 570                        }
 571                        if !capabilities
 572                            .supports_loaded_sources_request
 573                            .unwrap_or(false)
 574                        {
 575                            this.remove_pane_item(DebuggerPaneItem::LoadedSources, window, cx);
 576                        }
 577                    }
 578                    SessionEvent::RunInTerminal { request, sender } => this
 579                        .handle_run_in_terminal(request, sender.clone(), window, cx)
 580                        .detach_and_log_err(cx),
 581
 582                    _ => {}
 583                }
 584                cx.notify()
 585            }),
 586            cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
 587                this.serialize_layout(window, cx);
 588            }),
 589        ];
 590
 591        let mut pane_close_subscriptions = HashMap::default();
 592        let panes = if let Some(root) = serialized_pane_layout.and_then(|serialized_layout| {
 593            persistence::deserialize_pane_layout(
 594                serialized_layout.panes,
 595                dock_axis != serialized_layout.dock_axis,
 596                &workspace,
 597                &project,
 598                &stack_frame_list,
 599                &variable_list,
 600                &module_list,
 601                &console,
 602                &breakpoint_list,
 603                &loaded_source_list,
 604                &debug_terminal,
 605                &mut pane_close_subscriptions,
 606                window,
 607                cx,
 608            )
 609        }) {
 610            workspace::PaneGroup::with_root(root)
 611        } else {
 612            pane_close_subscriptions.clear();
 613
 614            let root = Self::default_pane_layout(
 615                project,
 616                &workspace,
 617                &stack_frame_list,
 618                &variable_list,
 619                &module_list,
 620                &loaded_source_list,
 621                &console,
 622                &breakpoint_list,
 623                dock_axis,
 624                &mut pane_close_subscriptions,
 625                window,
 626                cx,
 627            );
 628
 629            workspace::PaneGroup::with_root(root)
 630        };
 631
 632        Self {
 633            session,
 634            workspace,
 635            focus_handle,
 636            variable_list,
 637            _subscriptions,
 638            thread_id: None,
 639            _remote_id: None,
 640            stack_frame_list,
 641            session_id,
 642            panes,
 643            active_pane: None,
 644            module_list,
 645            console,
 646            breakpoint_list,
 647            loaded_sources_list: loaded_source_list,
 648            pane_close_subscriptions,
 649            debug_terminal,
 650            dock_axis,
 651            _schedule_serialize: None,
 652        }
 653    }
 654
 655    pub(crate) fn remove_pane_item(
 656        &mut self,
 657        item_kind: DebuggerPaneItem,
 658        window: &mut Window,
 659        cx: &mut Context<Self>,
 660    ) {
 661        if let Some((pane, item_id)) = self.panes.panes().iter().find_map(|pane| {
 662            Some(pane).zip(
 663                pane.read(cx)
 664                    .items()
 665                    .find(|item| {
 666                        item.act_as::<SubView>(cx)
 667                            .is_some_and(|view| view.read(cx).kind == item_kind)
 668                    })
 669                    .map(|item| item.item_id()),
 670            )
 671        }) {
 672            pane.update(cx, |pane, cx| {
 673                pane.remove_item(item_id, false, true, window, cx)
 674            })
 675        }
 676    }
 677
 678    pub(crate) fn has_pane_at_position(&self, position: Point<Pixels>) -> bool {
 679        self.panes.pane_at_pixel_position(position).is_some()
 680    }
 681
 682    pub(crate) fn resolve_scenario(
 683        &self,
 684        scenario: DebugScenario,
 685        task_context: TaskContext,
 686        buffer: Option<Entity<Buffer>>,
 687        window: &Window,
 688        cx: &mut Context<Self>,
 689    ) -> Task<Result<DebugTaskDefinition>> {
 690        let Some(workspace) = self.workspace.upgrade() else {
 691            return Task::ready(Err(anyhow!("no workspace")));
 692        };
 693        let project = workspace.read(cx).project().clone();
 694        let dap_store = project.read(cx).dap_store().downgrade();
 695        let task_store = project.read(cx).task_store().downgrade();
 696        let weak_project = project.downgrade();
 697        let weak_workspace = workspace.downgrade();
 698        cx.spawn_in(window, async move |this, cx| {
 699            let DebugScenario {
 700                adapter,
 701                label,
 702                build,
 703                request,
 704                initialize_args,
 705                tcp_connection,
 706                stop_on_entry,
 707            } = scenario;
 708            let request = if let Some(request) = request {
 709                request
 710            } else if let Some(build) = build {
 711                let Some(task) = task_store.update(cx, |this, cx| {
 712                    this.task_inventory().and_then(|inventory| {
 713                        inventory
 714                            .read(cx)
 715                            .task_template_by_label(buffer, &build, cx)
 716                    })
 717                })?
 718                else {
 719                    anyhow::bail!("Couldn't find task template for {:?}", build)
 720                };
 721                let Some(task) = task.resolve_task("debug-build-task", &task_context) else {
 722                    anyhow::bail!("Could not resolve task variables within a debug scenario");
 723                };
 724
 725                let terminal = project
 726                    .update_in(cx, |project, window, cx| {
 727                        project.create_terminal(
 728                            TerminalKind::Task(task.resolved.clone()),
 729                            window.window_handle(),
 730                            cx,
 731                        )
 732                    })?
 733                    .await?;
 734
 735                let terminal_view = cx.new_window_entity(|window, cx| {
 736                    TerminalView::new(
 737                        terminal.clone(),
 738                        weak_workspace,
 739                        None,
 740                        weak_project,
 741                        false,
 742                        window,
 743                        cx,
 744                    )
 745                })?;
 746
 747                this.update_in(cx, |this, window, cx| {
 748                    this.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
 749                    this.debug_terminal.update(cx, |debug_terminal, cx| {
 750                        debug_terminal.terminal = Some(terminal_view);
 751                        cx.notify();
 752                    });
 753                })?;
 754
 755                let exit_status = terminal
 756                    .read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
 757                    .await
 758                    .ok_or_else(|| anyhow!("Failed to wait for completed task"))?;
 759
 760                if !exit_status.success() {
 761                    anyhow::bail!("Build failed");
 762                }
 763
 764                dap_store
 765                    .update(cx, |this, cx| this.run_debug_locator(task.resolved, cx))?
 766                    .await?
 767            } else {
 768                return Err(anyhow!("No request or build provided"));
 769            };
 770            let request = match request {
 771                dap::DebugRequest::Launch(launch_request) => {
 772                    let cwd = match launch_request.cwd.as_deref().and_then(|path| path.to_str()) {
 773                        Some(cwd) => {
 774                            let substituted_cwd = substitute_variables_in_str(&cwd, &task_context)
 775                                .ok_or_else(|| anyhow!("Failed to substitute variables in cwd"))?;
 776                            Some(PathBuf::from(substituted_cwd))
 777                        }
 778                        None => None,
 779                    };
 780
 781                    let env = substitute_variables_in_map(
 782                        &launch_request.env.into_iter().collect(),
 783                        &task_context,
 784                    )
 785                    .ok_or_else(|| anyhow!("Failed to substitute variables in env"))?
 786                    .into_iter()
 787                    .collect();
 788                    let new_launch_request = LaunchRequest {
 789                        program: substitute_variables_in_str(
 790                            &launch_request.program,
 791                            &task_context,
 792                        )
 793                        .ok_or_else(|| anyhow!("Failed to substitute variables in program"))?,
 794                        args: launch_request
 795                            .args
 796                            .into_iter()
 797                            .map(|arg| substitute_variables_in_str(&arg, &task_context))
 798                            .collect::<Option<Vec<_>>>()
 799                            .ok_or_else(|| anyhow!("Failed to substitute variables in args"))?,
 800                        cwd,
 801                        env,
 802                    };
 803
 804                    dap::DebugRequest::Launch(new_launch_request)
 805                }
 806                request @ dap::DebugRequest::Attach(_) => request,
 807            };
 808            Ok(DebugTaskDefinition {
 809                label,
 810                adapter: DebugAdapterName(adapter),
 811                request,
 812                initialize_args,
 813                stop_on_entry,
 814                tcp_connection,
 815            })
 816        })
 817    }
 818
 819    fn handle_run_in_terminal(
 820        &self,
 821        request: &RunInTerminalRequestArguments,
 822        mut sender: mpsc::Sender<Result<u32>>,
 823        window: &mut Window,
 824        cx: &mut Context<Self>,
 825    ) -> Task<Result<()>> {
 826        let running = cx.entity();
 827        let Ok(project) = self
 828            .workspace
 829            .update(cx, |workspace, _| workspace.project().clone())
 830        else {
 831            return Task::ready(Err(anyhow!("no workspace")));
 832        };
 833        let session = self.session.read(cx);
 834
 835        let cwd = Some(&request.cwd)
 836            .filter(|cwd| cwd.len() > 0)
 837            .map(PathBuf::from)
 838            .or_else(|| session.binary().cwd.clone());
 839
 840        let mut args = request.args.clone();
 841
 842        // Handle special case for NodeJS debug adapter
 843        // If only the Node binary path is provided, we set the command to None
 844        // This prevents the NodeJS REPL from appearing, which is not the desired behavior
 845        // The expected usage is for users to provide their own Node command, e.g., `node test.js`
 846        // This allows the NodeJS debug client to attach correctly
 847        let command = if args.len() > 1 {
 848            Some(args.remove(0))
 849        } else {
 850            None
 851        };
 852
 853        let mut envs: HashMap<String, String> = Default::default();
 854        if let Some(Value::Object(env)) = &request.env {
 855            for (key, value) in env {
 856                let value_str = match (key.as_str(), value) {
 857                    (_, Value::String(value)) => value,
 858                    _ => continue,
 859                };
 860
 861                envs.insert(key.clone(), value_str.clone());
 862            }
 863        }
 864
 865        let shell = project.read(cx).terminal_settings(&cwd, cx).shell.clone();
 866        let kind = if let Some(command) = command {
 867            let title = request.title.clone().unwrap_or(command.clone());
 868            TerminalKind::Task(task::SpawnInTerminal {
 869                id: task::TaskId("debug".to_string()),
 870                full_label: title.clone(),
 871                label: title.clone(),
 872                command: command.clone(),
 873                args,
 874                command_label: title.clone(),
 875                cwd,
 876                env: envs,
 877                use_new_terminal: true,
 878                allow_concurrent_runs: true,
 879                reveal: task::RevealStrategy::NoFocus,
 880                reveal_target: task::RevealTarget::Dock,
 881                hide: task::HideStrategy::Never,
 882                shell,
 883                show_summary: false,
 884                show_command: false,
 885                show_rerun: false,
 886            })
 887        } else {
 888            TerminalKind::Shell(cwd.map(|c| c.to_path_buf()))
 889        };
 890
 891        let workspace = self.workspace.clone();
 892        let weak_project = project.downgrade();
 893
 894        let terminal_task = project.update(cx, |project, cx| {
 895            project.create_terminal(kind, window.window_handle(), cx)
 896        });
 897        let terminal_task = cx.spawn_in(window, async move |_, cx| {
 898            let terminal = terminal_task.await?;
 899
 900            let terminal_view = cx.new_window_entity(|window, cx| {
 901                TerminalView::new(
 902                    terminal.clone(),
 903                    workspace,
 904                    None,
 905                    weak_project,
 906                    false,
 907                    window,
 908                    cx,
 909                )
 910            })?;
 911
 912            running.update_in(cx, |running, window, cx| {
 913                running.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
 914                running.debug_terminal.update(cx, |debug_terminal, cx| {
 915                    debug_terminal.terminal = Some(terminal_view);
 916                    cx.notify();
 917                });
 918            })?;
 919
 920            terminal.read_with(cx, |terminal, _| {
 921                terminal
 922                    .pty_info
 923                    .pid()
 924                    .map(|pid| pid.as_u32())
 925                    .ok_or_else(|| anyhow!("Terminal was spawned but PID was not available"))
 926            })?
 927        });
 928
 929        cx.background_spawn(async move { anyhow::Ok(sender.send(terminal_task.await).await?) })
 930    }
 931
 932    fn create_sub_view(
 933        &self,
 934        item_kind: DebuggerPaneItem,
 935        _pane: &Entity<Pane>,
 936        cx: &mut Context<Self>,
 937    ) -> Box<dyn ItemHandle> {
 938        match item_kind {
 939            DebuggerPaneItem::Console => {
 940                let weak_console = self.console.clone().downgrade();
 941
 942                Box::new(SubView::new(
 943                    self.console.focus_handle(cx),
 944                    self.console.clone().into(),
 945                    item_kind,
 946                    Some(Box::new(move |cx| {
 947                        weak_console
 948                            .read_with(cx, |console, cx| console.show_indicator(cx))
 949                            .unwrap_or_default()
 950                    })),
 951                    cx,
 952                ))
 953            }
 954            DebuggerPaneItem::Variables => Box::new(SubView::new(
 955                self.variable_list.focus_handle(cx),
 956                self.variable_list.clone().into(),
 957                item_kind,
 958                None,
 959                cx,
 960            )),
 961            DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
 962                self.breakpoint_list.focus_handle(cx),
 963                self.breakpoint_list.clone().into(),
 964                item_kind,
 965                None,
 966                cx,
 967            )),
 968            DebuggerPaneItem::Frames => Box::new(SubView::new(
 969                self.stack_frame_list.focus_handle(cx),
 970                self.stack_frame_list.clone().into(),
 971                item_kind,
 972                None,
 973                cx,
 974            )),
 975            DebuggerPaneItem::Modules => Box::new(SubView::new(
 976                self.module_list.focus_handle(cx),
 977                self.module_list.clone().into(),
 978                item_kind,
 979                None,
 980                cx,
 981            )),
 982            DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
 983                self.loaded_sources_list.focus_handle(cx),
 984                self.loaded_sources_list.clone().into(),
 985                item_kind,
 986                None,
 987                cx,
 988            )),
 989            DebuggerPaneItem::Terminal => Box::new(SubView::new(
 990                self.debug_terminal.focus_handle(cx),
 991                self.debug_terminal.clone().into(),
 992                item_kind,
 993                None,
 994                cx,
 995            )),
 996        }
 997    }
 998
 999    pub(crate) fn ensure_pane_item(
1000        &mut self,
1001        item_kind: DebuggerPaneItem,
1002        window: &mut Window,
1003        cx: &mut Context<Self>,
1004    ) {
1005        if self.pane_items_status(cx).get(&item_kind) == Some(&true) {
1006            return;
1007        };
1008        let pane = self.panes.last_pane();
1009        let sub_view = self.create_sub_view(item_kind, &pane, cx);
1010
1011        pane.update(cx, |pane, cx| {
1012            pane.add_item_inner(sub_view, false, false, false, None, window, cx);
1013        })
1014    }
1015
1016    pub(crate) fn add_pane_item(
1017        &mut self,
1018        item_kind: DebuggerPaneItem,
1019        position: Point<Pixels>,
1020        window: &mut Window,
1021        cx: &mut Context<Self>,
1022    ) {
1023        debug_assert!(
1024            item_kind.is_supported(self.session.read(cx).capabilities()),
1025            "We should only allow adding supported item kinds"
1026        );
1027
1028        if let Some(pane) = self.panes.pane_at_pixel_position(position) {
1029            let sub_view = self.create_sub_view(item_kind, pane, cx);
1030
1031            pane.update(cx, |pane, cx| {
1032                pane.add_item(sub_view, false, false, None, window, cx);
1033            })
1034        }
1035    }
1036
1037    pub(crate) fn pane_items_status(&self, cx: &App) -> IndexMap<DebuggerPaneItem, bool> {
1038        let caps = self.session.read(cx).capabilities();
1039        let mut pane_item_status = IndexMap::from_iter(
1040            DebuggerPaneItem::all()
1041                .iter()
1042                .filter(|kind| kind.is_supported(&caps))
1043                .map(|kind| (*kind, false)),
1044        );
1045        self.panes.panes().iter().for_each(|pane| {
1046            pane.read(cx)
1047                .items()
1048                .filter_map(|item| item.act_as::<SubView>(cx))
1049                .for_each(|view| {
1050                    pane_item_status.insert(view.read(cx).kind, true);
1051                });
1052        });
1053
1054        pane_item_status
1055    }
1056
1057    pub(crate) fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1058        if self._schedule_serialize.is_none() {
1059            self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
1060                cx.background_executor()
1061                    .timer(Duration::from_millis(100))
1062                    .await;
1063
1064                let Some((adapter_name, pane_layout)) = this
1065                    .read_with(cx, |this, cx| {
1066                        let adapter_name = this.session.read(cx).adapter();
1067                        (
1068                            adapter_name,
1069                            persistence::build_serialized_layout(
1070                                &this.panes.root,
1071                                this.dock_axis,
1072                                cx,
1073                            ),
1074                        )
1075                    })
1076                    .ok()
1077                else {
1078                    return;
1079                };
1080
1081                persistence::serialize_pane_layout(adapter_name, pane_layout)
1082                    .await
1083                    .log_err();
1084
1085                this.update(cx, |this, _| {
1086                    this._schedule_serialize.take();
1087                })
1088                .ok();
1089            }));
1090        }
1091    }
1092
1093    pub(crate) fn handle_pane_event(
1094        this: &mut RunningState,
1095        source_pane: &Entity<Pane>,
1096        event: &Event,
1097        window: &mut Window,
1098        cx: &mut Context<RunningState>,
1099    ) {
1100        this.serialize_layout(window, cx);
1101        match event {
1102            Event::Remove { .. } => {
1103                let _did_find_pane = this.panes.remove(&source_pane).is_ok();
1104                debug_assert!(_did_find_pane);
1105                cx.notify();
1106            }
1107            Event::Focus => {
1108                this.active_pane = Some(source_pane.clone());
1109            }
1110            Event::ZoomIn => {
1111                source_pane.update(cx, |pane, cx| {
1112                    pane.set_zoomed(true, cx);
1113                });
1114                cx.notify();
1115            }
1116            Event::ZoomOut => {
1117                source_pane.update(cx, |pane, cx| {
1118                    pane.set_zoomed(false, cx);
1119                });
1120                cx.notify();
1121            }
1122            _ => {}
1123        }
1124    }
1125
1126    pub(crate) fn activate_pane_in_direction(
1127        &mut self,
1128        direction: SplitDirection,
1129        window: &mut Window,
1130        cx: &mut Context<Self>,
1131    ) {
1132        if let Some(pane) = self
1133            .active_pane
1134            .as_ref()
1135            .and_then(|pane| self.panes.find_pane_in_direction(pane, direction, cx))
1136        {
1137            window.focus(&pane.focus_handle(cx));
1138        } else {
1139            self.workspace
1140                .update(cx, |workspace, cx| {
1141                    workspace.activate_pane_in_direction(direction, window, cx)
1142                })
1143                .ok();
1144        }
1145    }
1146
1147    pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) {
1148        if self.thread_id.is_some() {
1149            self.stack_frame_list
1150                .update(cx, |list, cx| list.go_to_selected_stack_frame(window, cx));
1151        }
1152    }
1153
1154    pub(crate) fn has_open_context_menu(&self, cx: &App) -> bool {
1155        self.variable_list.read(cx).has_open_context_menu()
1156    }
1157
1158    pub fn session(&self) -> &Entity<Session> {
1159        &self.session
1160    }
1161
1162    pub fn session_id(&self) -> SessionId {
1163        self.session_id
1164    }
1165
1166    pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option<dap::StackFrameId> {
1167        self.stack_frame_list.read(cx).selected_stack_frame_id()
1168    }
1169
1170    #[cfg(test)]
1171    pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
1172        &self.stack_frame_list
1173    }
1174
1175    #[cfg(test)]
1176    pub fn console(&self) -> &Entity<Console> {
1177        &self.console
1178    }
1179
1180    #[cfg(test)]
1181    pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
1182        &self.module_list
1183    }
1184
1185    pub(crate) fn activate_item(&self, item: DebuggerPaneItem, window: &mut Window, cx: &mut App) {
1186        let (variable_list_position, pane) = self
1187            .panes
1188            .panes()
1189            .into_iter()
1190            .find_map(|pane| {
1191                pane.read(cx)
1192                    .items_of_type::<SubView>()
1193                    .position(|view| view.read(cx).view_kind() == item)
1194                    .map(|view| (view, pane))
1195            })
1196            .unwrap();
1197        pane.update(cx, |this, cx| {
1198            this.activate_item(variable_list_position, true, true, window, cx);
1199        })
1200    }
1201
1202    #[cfg(test)]
1203    pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
1204        &self.variable_list
1205    }
1206
1207    #[cfg(test)]
1208    pub(crate) fn serialized_layout(&self, cx: &App) -> SerializedLayout {
1209        persistence::build_serialized_layout(&self.panes.root, self.dock_axis, cx)
1210    }
1211
1212    pub fn capabilities(&self, cx: &App) -> Capabilities {
1213        self.session().read(cx).capabilities().clone()
1214    }
1215
1216    pub fn select_current_thread(
1217        &mut self,
1218        threads: &Vec<(Thread, ThreadStatus)>,
1219        window: &mut Window,
1220        cx: &mut Context<Self>,
1221    ) {
1222        let selected_thread = self
1223            .thread_id
1224            .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0))
1225            .or_else(|| threads.first());
1226
1227        let Some((selected_thread, _)) = selected_thread else {
1228            return;
1229        };
1230
1231        if Some(ThreadId(selected_thread.id)) != self.thread_id {
1232            self.select_thread(ThreadId(selected_thread.id), window, cx);
1233        }
1234    }
1235
1236    pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
1237        self.thread_id
1238    }
1239
1240    pub fn thread_status(&self, cx: &App) -> Option<ThreadStatus> {
1241        self.thread_id
1242            .map(|id| self.session().read(cx).thread_status(id))
1243    }
1244
1245    fn select_thread(&mut self, thread_id: ThreadId, window: &mut Window, cx: &mut Context<Self>) {
1246        if self.thread_id.is_some_and(|id| id == thread_id) {
1247            return;
1248        }
1249
1250        self.thread_id = Some(thread_id);
1251
1252        self.stack_frame_list
1253            .update(cx, |list, cx| list.schedule_refresh(true, window, cx));
1254    }
1255
1256    pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
1257        let Some(thread_id) = self.thread_id else {
1258            return;
1259        };
1260
1261        self.session().update(cx, |state, cx| {
1262            state.continue_thread(thread_id, cx);
1263        });
1264    }
1265
1266    pub fn step_over(&mut self, cx: &mut Context<Self>) {
1267        let Some(thread_id) = self.thread_id else {
1268            return;
1269        };
1270
1271        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1272
1273        self.session().update(cx, |state, cx| {
1274            state.step_over(thread_id, granularity, cx);
1275        });
1276    }
1277
1278    pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
1279        let Some(thread_id) = self.thread_id else {
1280            return;
1281        };
1282
1283        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1284
1285        self.session().update(cx, |state, cx| {
1286            state.step_in(thread_id, granularity, cx);
1287        });
1288    }
1289
1290    pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
1291        let Some(thread_id) = self.thread_id else {
1292            return;
1293        };
1294
1295        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1296
1297        self.session().update(cx, |state, cx| {
1298            state.step_out(thread_id, granularity, cx);
1299        });
1300    }
1301
1302    pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
1303        let Some(thread_id) = self.thread_id else {
1304            return;
1305        };
1306
1307        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1308
1309        self.session().update(cx, |state, cx| {
1310            state.step_back(thread_id, granularity, cx);
1311        });
1312    }
1313
1314    pub fn restart_session(&self, cx: &mut Context<Self>) {
1315        self.session().update(cx, |state, cx| {
1316            state.restart(None, cx);
1317        });
1318    }
1319
1320    pub fn pause_thread(&self, cx: &mut Context<Self>) {
1321        let Some(thread_id) = self.thread_id else {
1322            return;
1323        };
1324
1325        self.session().update(cx, |state, cx| {
1326            state.pause_thread(thread_id, cx);
1327        });
1328    }
1329
1330    pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
1331        self.workspace
1332            .update(cx, |workspace, cx| {
1333                workspace
1334                    .project()
1335                    .read(cx)
1336                    .breakpoint_store()
1337                    .update(cx, |store, cx| {
1338                        store.remove_active_position(Some(self.session_id), cx)
1339                    })
1340            })
1341            .log_err();
1342
1343        self.session.update(cx, |session, cx| {
1344            session.shutdown(cx).detach();
1345        })
1346    }
1347
1348    pub fn stop_thread(&self, cx: &mut Context<Self>) {
1349        let Some(thread_id) = self.thread_id else {
1350            return;
1351        };
1352
1353        self.workspace
1354            .update(cx, |workspace, cx| {
1355                workspace
1356                    .project()
1357                    .read(cx)
1358                    .breakpoint_store()
1359                    .update(cx, |store, cx| {
1360                        store.remove_active_position(Some(self.session_id), cx)
1361                    })
1362            })
1363            .log_err();
1364
1365        self.session().update(cx, |state, cx| {
1366            state.terminate_threads(Some(vec![thread_id; 1]), cx);
1367        });
1368    }
1369
1370    #[expect(
1371        unused,
1372        reason = "Support for disconnecting a client is not wired through yet"
1373    )]
1374    pub fn disconnect_client(&self, cx: &mut Context<Self>) {
1375        self.session().update(cx, |state, cx| {
1376            state.disconnect_client(cx);
1377        });
1378    }
1379
1380    pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context<Self>) {
1381        self.session.update(cx, |session, cx| {
1382            session.toggle_ignore_breakpoints(cx).detach();
1383        });
1384    }
1385
1386    pub(crate) fn thread_dropdown(
1387        &self,
1388        window: &mut Window,
1389        cx: &mut Context<'_, RunningState>,
1390    ) -> DropdownMenu {
1391        let state = cx.entity();
1392        let threads = self.session.update(cx, |this, cx| this.threads(cx));
1393        let selected_thread_name = threads
1394            .iter()
1395            .find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
1396            .map(|(thread, _)| thread.name.clone())
1397            .unwrap_or("Threads".to_owned());
1398        DropdownMenu::new(
1399            ("thread-list", self.session_id.0),
1400            selected_thread_name,
1401            ContextMenu::build_eager(window, cx, move |mut this, _, _| {
1402                for (thread, _) in threads {
1403                    let state = state.clone();
1404                    let thread_id = thread.id;
1405                    this = this.entry(thread.name, None, move |window, cx| {
1406                        state.update(cx, |state, cx| {
1407                            state.select_thread(ThreadId(thread_id), window, cx);
1408                        });
1409                    });
1410                }
1411                this
1412            }),
1413        )
1414    }
1415
1416    fn default_pane_layout(
1417        project: Entity<Project>,
1418        workspace: &WeakEntity<Workspace>,
1419        stack_frame_list: &Entity<StackFrameList>,
1420        variable_list: &Entity<VariableList>,
1421        module_list: &Entity<ModuleList>,
1422        loaded_source_list: &Entity<LoadedSourceList>,
1423        console: &Entity<Console>,
1424        breakpoints: &Entity<BreakpointList>,
1425        dock_axis: Axis,
1426        subscriptions: &mut HashMap<EntityId, Subscription>,
1427        window: &mut Window,
1428        cx: &mut Context<'_, RunningState>,
1429    ) -> Member {
1430        let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1431        leftmost_pane.update(cx, |this, cx| {
1432            this.add_item(
1433                Box::new(SubView::new(
1434                    this.focus_handle(cx),
1435                    stack_frame_list.clone().into(),
1436                    DebuggerPaneItem::Frames,
1437                    None,
1438                    cx,
1439                )),
1440                true,
1441                false,
1442                None,
1443                window,
1444                cx,
1445            );
1446            this.add_item(
1447                Box::new(SubView::new(
1448                    breakpoints.focus_handle(cx),
1449                    breakpoints.clone().into(),
1450                    DebuggerPaneItem::BreakpointList,
1451                    None,
1452                    cx,
1453                )),
1454                true,
1455                false,
1456                None,
1457                window,
1458                cx,
1459            );
1460            this.activate_item(0, false, false, window, cx);
1461        });
1462        let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1463
1464        center_pane.update(cx, |this, cx| {
1465            this.add_item(
1466                Box::new(SubView::new(
1467                    variable_list.focus_handle(cx),
1468                    variable_list.clone().into(),
1469                    DebuggerPaneItem::Variables,
1470                    None,
1471                    cx,
1472                )),
1473                true,
1474                false,
1475                None,
1476                window,
1477                cx,
1478            );
1479            this.add_item(
1480                Box::new(SubView::new(
1481                    module_list.focus_handle(cx),
1482                    module_list.clone().into(),
1483                    DebuggerPaneItem::Modules,
1484                    None,
1485                    cx,
1486                )),
1487                false,
1488                false,
1489                None,
1490                window,
1491                cx,
1492            );
1493
1494            this.add_item(
1495                Box::new(SubView::new(
1496                    loaded_source_list.focus_handle(cx),
1497                    loaded_source_list.clone().into(),
1498                    DebuggerPaneItem::LoadedSources,
1499                    None,
1500                    cx,
1501                )),
1502                false,
1503                false,
1504                None,
1505                window,
1506                cx,
1507            );
1508            this.activate_item(0, false, false, window, cx);
1509        });
1510
1511        let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1512        rightmost_pane.update(cx, |this, cx| {
1513            let weak_console = console.downgrade();
1514            this.add_item(
1515                Box::new(SubView::new(
1516                    this.focus_handle(cx),
1517                    console.clone().into(),
1518                    DebuggerPaneItem::Console,
1519                    Some(Box::new(move |cx| {
1520                        weak_console
1521                            .read_with(cx, |console, cx| console.show_indicator(cx))
1522                            .unwrap_or_default()
1523                    })),
1524                    cx,
1525                )),
1526                true,
1527                false,
1528                None,
1529                window,
1530                cx,
1531            );
1532        });
1533
1534        subscriptions.extend(
1535            [&leftmost_pane, &center_pane, &rightmost_pane]
1536                .into_iter()
1537                .map(|entity| {
1538                    (
1539                        entity.entity_id(),
1540                        cx.subscribe_in(entity, window, Self::handle_pane_event),
1541                    )
1542                }),
1543        );
1544
1545        let group_root = workspace::PaneAxis::new(
1546            dock_axis.invert(),
1547            [leftmost_pane, center_pane, rightmost_pane]
1548                .into_iter()
1549                .map(workspace::Member::Pane)
1550                .collect(),
1551        );
1552
1553        Member::Axis(group_root)
1554    }
1555
1556    pub(crate) fn invert_axies(&mut self) {
1557        self.dock_axis = self.dock_axis.invert();
1558        self.panes.invert_axies();
1559    }
1560}
1561
1562impl EventEmitter<DebugPanelItemEvent> for RunningState {}
1563
1564impl Focusable for RunningState {
1565    fn focus_handle(&self, _: &App) -> FocusHandle {
1566        self.focus_handle.clone()
1567    }
1568}