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, WorktreeId,
  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    BuildTaskDefinition, DebugScenario, LaunchRequest, ShellBuilder, SpawnInTerminal, TaskContext,
  42    substitute_variables_in_map, 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        worktree_id: Option<WorktreeId>,
 688        window: &Window,
 689        cx: &mut Context<Self>,
 690    ) -> Task<Result<DebugTaskDefinition>> {
 691        let Some(workspace) = self.workspace.upgrade() else {
 692            return Task::ready(Err(anyhow!("no workspace")));
 693        };
 694        let project = workspace.read(cx).project().clone();
 695        let dap_store = project.read(cx).dap_store().downgrade();
 696        let task_store = project.read(cx).task_store().downgrade();
 697        let weak_project = project.downgrade();
 698        let weak_workspace = workspace.downgrade();
 699        let is_local = project.read(cx).is_local();
 700        cx.spawn_in(window, async move |this, cx| {
 701            let DebugScenario {
 702                adapter,
 703                label,
 704                build,
 705                request,
 706                initialize_args,
 707                tcp_connection,
 708                stop_on_entry,
 709            } = scenario;
 710            let build_output = if let Some(build) = build {
 711                let (task, locator_name) = match build {
 712                    BuildTaskDefinition::Template {
 713                        task_template,
 714                        locator_name,
 715                    } => (task_template, locator_name),
 716                    BuildTaskDefinition::ByName(ref label) => {
 717                        let Some(task) = task_store.update(cx, |this, cx| {
 718                            this.task_inventory().and_then(|inventory| {
 719                                inventory.read(cx).task_template_by_label(
 720                                    buffer,
 721                                    worktree_id,
 722                                    &label,
 723                                    cx,
 724                                )
 725                            })
 726                        })?
 727                        else {
 728                            anyhow::bail!("Couldn't find task template for {:?}", build)
 729                        };
 730                        (task, None)
 731                    }
 732                };
 733                let locator_name = if let Some(locator_name) = locator_name {
 734                    debug_assert!(request.is_none());
 735                    Some(locator_name)
 736                } else if request.is_none() {
 737                    dap_store
 738                        .update(cx, |this, cx| {
 739                            this.debug_scenario_for_build_task(task.clone(), adapter.clone(), cx)
 740                                .and_then(|scenario| match scenario.build {
 741                                    Some(BuildTaskDefinition::Template {
 742                                        locator_name, ..
 743                                    }) => locator_name,
 744                                    _ => None,
 745                                })
 746                        })
 747                        .ok()
 748                        .flatten()
 749                } else {
 750                    None
 751                };
 752
 753                let Some(task) = task.resolve_task("debug-build-task", &task_context) else {
 754                    anyhow::bail!("Could not resolve task variables within a debug scenario");
 755                };
 756
 757                let builder = ShellBuilder::new(is_local, &task.resolved.shell);
 758                let command_label = builder.command_label(&task.resolved.command_label);
 759                let (command, args) =
 760                    builder.build(task.resolved.command.clone(), &task.resolved.args);
 761
 762                let task_with_shell = SpawnInTerminal {
 763                    command_label,
 764                    command,
 765                    args,
 766                    ..task.resolved.clone()
 767                };
 768                let terminal = project
 769                    .update_in(cx, |project, window, cx| {
 770                        project.create_terminal(
 771                            TerminalKind::Task(task_with_shell.clone()),
 772                            window.window_handle(),
 773                            cx,
 774                        )
 775                    })?
 776                    .await?;
 777
 778                let terminal_view = cx.new_window_entity(|window, cx| {
 779                    TerminalView::new(
 780                        terminal.clone(),
 781                        weak_workspace,
 782                        None,
 783                        weak_project,
 784                        false,
 785                        window,
 786                        cx,
 787                    )
 788                })?;
 789
 790                this.update_in(cx, |this, window, cx| {
 791                    this.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
 792                    this.debug_terminal.update(cx, |debug_terminal, cx| {
 793                        debug_terminal.terminal = Some(terminal_view);
 794                        cx.notify();
 795                    });
 796                })?;
 797
 798                let exit_status = terminal
 799                    .read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
 800                    .await
 801                    .ok_or_else(|| anyhow!("Failed to wait for completed task"))?;
 802
 803                if !exit_status.success() {
 804                    anyhow::bail!("Build failed");
 805                }
 806                Some((task.resolved.clone(), locator_name))
 807            } else {
 808                None
 809            };
 810            let request = if let Some(request) = request {
 811                request
 812            } else if let Some((task, locator_name)) = build_output {
 813                let locator_name = locator_name
 814                    .ok_or_else(|| anyhow!("Could not find a valid locator for a build task"))?;
 815                dap_store
 816                    .update(cx, |this, cx| {
 817                        this.run_debug_locator(&locator_name, task, cx)
 818                    })?
 819                    .await?
 820            } else {
 821                return Err(anyhow!("No request or build provided"));
 822            };
 823            let request = match request {
 824                dap::DebugRequest::Launch(launch_request) => {
 825                    let cwd = match launch_request.cwd.as_deref().and_then(|path| path.to_str()) {
 826                        Some(cwd) => {
 827                            let substituted_cwd = substitute_variables_in_str(&cwd, &task_context)
 828                                .ok_or_else(|| anyhow!("Failed to substitute variables in cwd"))?;
 829                            Some(PathBuf::from(substituted_cwd))
 830                        }
 831                        None => None,
 832                    };
 833
 834                    let env = substitute_variables_in_map(
 835                        &launch_request.env.into_iter().collect(),
 836                        &task_context,
 837                    )
 838                    .ok_or_else(|| anyhow!("Failed to substitute variables in env"))?
 839                    .into_iter()
 840                    .collect();
 841                    let new_launch_request = LaunchRequest {
 842                        program: substitute_variables_in_str(
 843                            &launch_request.program,
 844                            &task_context,
 845                        )
 846                        .ok_or_else(|| anyhow!("Failed to substitute variables in program"))?,
 847                        args: launch_request
 848                            .args
 849                            .into_iter()
 850                            .map(|arg| substitute_variables_in_str(&arg, &task_context))
 851                            .collect::<Option<Vec<_>>>()
 852                            .ok_or_else(|| anyhow!("Failed to substitute variables in args"))?,
 853                        cwd,
 854                        env,
 855                    };
 856
 857                    dap::DebugRequest::Launch(new_launch_request)
 858                }
 859                request @ dap::DebugRequest::Attach(_) => request,
 860            };
 861            Ok(DebugTaskDefinition {
 862                label,
 863                adapter: DebugAdapterName(adapter),
 864                request,
 865                initialize_args,
 866                stop_on_entry,
 867                tcp_connection,
 868            })
 869        })
 870    }
 871
 872    fn handle_run_in_terminal(
 873        &self,
 874        request: &RunInTerminalRequestArguments,
 875        mut sender: mpsc::Sender<Result<u32>>,
 876        window: &mut Window,
 877        cx: &mut Context<Self>,
 878    ) -> Task<Result<()>> {
 879        let running = cx.entity();
 880        let Ok(project) = self
 881            .workspace
 882            .update(cx, |workspace, _| workspace.project().clone())
 883        else {
 884            return Task::ready(Err(anyhow!("no workspace")));
 885        };
 886        let session = self.session.read(cx);
 887
 888        let cwd = Some(&request.cwd)
 889            .filter(|cwd| cwd.len() > 0)
 890            .map(PathBuf::from)
 891            .or_else(|| session.binary().cwd.clone());
 892
 893        let mut args = request.args.clone();
 894
 895        // Handle special case for NodeJS debug adapter
 896        // If only the Node binary path is provided, we set the command to None
 897        // This prevents the NodeJS REPL from appearing, which is not the desired behavior
 898        // The expected usage is for users to provide their own Node command, e.g., `node test.js`
 899        // This allows the NodeJS debug client to attach correctly
 900        let command = if args.len() > 1 {
 901            Some(args.remove(0))
 902        } else {
 903            None
 904        };
 905
 906        let mut envs: HashMap<String, String> = Default::default();
 907        if let Some(Value::Object(env)) = &request.env {
 908            for (key, value) in env {
 909                let value_str = match (key.as_str(), value) {
 910                    (_, Value::String(value)) => value,
 911                    _ => continue,
 912                };
 913
 914                envs.insert(key.clone(), value_str.clone());
 915            }
 916        }
 917
 918        let shell = project.read(cx).terminal_settings(&cwd, cx).shell.clone();
 919        let kind = if let Some(command) = command {
 920            let title = request.title.clone().unwrap_or(command.clone());
 921            TerminalKind::Task(task::SpawnInTerminal {
 922                id: task::TaskId("debug".to_string()),
 923                full_label: title.clone(),
 924                label: title.clone(),
 925                command: command.clone(),
 926                args,
 927                command_label: title.clone(),
 928                cwd,
 929                env: envs,
 930                use_new_terminal: true,
 931                allow_concurrent_runs: true,
 932                reveal: task::RevealStrategy::NoFocus,
 933                reveal_target: task::RevealTarget::Dock,
 934                hide: task::HideStrategy::Never,
 935                shell,
 936                show_summary: false,
 937                show_command: false,
 938                show_rerun: false,
 939            })
 940        } else {
 941            TerminalKind::Shell(cwd.map(|c| c.to_path_buf()))
 942        };
 943
 944        let workspace = self.workspace.clone();
 945        let weak_project = project.downgrade();
 946
 947        let terminal_task = project.update(cx, |project, cx| {
 948            project.create_terminal(kind, window.window_handle(), cx)
 949        });
 950        let terminal_task = cx.spawn_in(window, async move |_, cx| {
 951            let terminal = terminal_task.await?;
 952
 953            let terminal_view = cx.new_window_entity(|window, cx| {
 954                TerminalView::new(
 955                    terminal.clone(),
 956                    workspace,
 957                    None,
 958                    weak_project,
 959                    false,
 960                    window,
 961                    cx,
 962                )
 963            })?;
 964
 965            running.update_in(cx, |running, window, cx| {
 966                running.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
 967                running.debug_terminal.update(cx, |debug_terminal, cx| {
 968                    debug_terminal.terminal = Some(terminal_view);
 969                    cx.notify();
 970                });
 971            })?;
 972
 973            terminal.read_with(cx, |terminal, _| {
 974                terminal
 975                    .pty_info
 976                    .pid()
 977                    .map(|pid| pid.as_u32())
 978                    .ok_or_else(|| anyhow!("Terminal was spawned but PID was not available"))
 979            })?
 980        });
 981
 982        cx.background_spawn(async move { anyhow::Ok(sender.send(terminal_task.await).await?) })
 983    }
 984
 985    fn create_sub_view(
 986        &self,
 987        item_kind: DebuggerPaneItem,
 988        _pane: &Entity<Pane>,
 989        cx: &mut Context<Self>,
 990    ) -> Box<dyn ItemHandle> {
 991        match item_kind {
 992            DebuggerPaneItem::Console => {
 993                let weak_console = self.console.clone().downgrade();
 994
 995                Box::new(SubView::new(
 996                    self.console.focus_handle(cx),
 997                    self.console.clone().into(),
 998                    item_kind,
 999                    Some(Box::new(move |cx| {
1000                        weak_console
1001                            .read_with(cx, |console, cx| console.show_indicator(cx))
1002                            .unwrap_or_default()
1003                    })),
1004                    cx,
1005                ))
1006            }
1007            DebuggerPaneItem::Variables => Box::new(SubView::new(
1008                self.variable_list.focus_handle(cx),
1009                self.variable_list.clone().into(),
1010                item_kind,
1011                None,
1012                cx,
1013            )),
1014            DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
1015                self.breakpoint_list.focus_handle(cx),
1016                self.breakpoint_list.clone().into(),
1017                item_kind,
1018                None,
1019                cx,
1020            )),
1021            DebuggerPaneItem::Frames => Box::new(SubView::new(
1022                self.stack_frame_list.focus_handle(cx),
1023                self.stack_frame_list.clone().into(),
1024                item_kind,
1025                None,
1026                cx,
1027            )),
1028            DebuggerPaneItem::Modules => Box::new(SubView::new(
1029                self.module_list.focus_handle(cx),
1030                self.module_list.clone().into(),
1031                item_kind,
1032                None,
1033                cx,
1034            )),
1035            DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
1036                self.loaded_sources_list.focus_handle(cx),
1037                self.loaded_sources_list.clone().into(),
1038                item_kind,
1039                None,
1040                cx,
1041            )),
1042            DebuggerPaneItem::Terminal => Box::new(SubView::new(
1043                self.debug_terminal.focus_handle(cx),
1044                self.debug_terminal.clone().into(),
1045                item_kind,
1046                None,
1047                cx,
1048            )),
1049        }
1050    }
1051
1052    pub(crate) fn ensure_pane_item(
1053        &mut self,
1054        item_kind: DebuggerPaneItem,
1055        window: &mut Window,
1056        cx: &mut Context<Self>,
1057    ) {
1058        if self.pane_items_status(cx).get(&item_kind) == Some(&true) {
1059            return;
1060        };
1061        let pane = self.panes.last_pane();
1062        let sub_view = self.create_sub_view(item_kind, &pane, cx);
1063
1064        pane.update(cx, |pane, cx| {
1065            pane.add_item_inner(sub_view, false, false, false, None, window, cx);
1066        })
1067    }
1068
1069    pub(crate) fn add_pane_item(
1070        &mut self,
1071        item_kind: DebuggerPaneItem,
1072        position: Point<Pixels>,
1073        window: &mut Window,
1074        cx: &mut Context<Self>,
1075    ) {
1076        debug_assert!(
1077            item_kind.is_supported(self.session.read(cx).capabilities()),
1078            "We should only allow adding supported item kinds"
1079        );
1080
1081        if let Some(pane) = self.panes.pane_at_pixel_position(position) {
1082            let sub_view = self.create_sub_view(item_kind, pane, cx);
1083
1084            pane.update(cx, |pane, cx| {
1085                pane.add_item(sub_view, false, false, None, window, cx);
1086            })
1087        }
1088    }
1089
1090    pub(crate) fn pane_items_status(&self, cx: &App) -> IndexMap<DebuggerPaneItem, bool> {
1091        let caps = self.session.read(cx).capabilities();
1092        let mut pane_item_status = IndexMap::from_iter(
1093            DebuggerPaneItem::all()
1094                .iter()
1095                .filter(|kind| kind.is_supported(&caps))
1096                .map(|kind| (*kind, false)),
1097        );
1098        self.panes.panes().iter().for_each(|pane| {
1099            pane.read(cx)
1100                .items()
1101                .filter_map(|item| item.act_as::<SubView>(cx))
1102                .for_each(|view| {
1103                    pane_item_status.insert(view.read(cx).kind, true);
1104                });
1105        });
1106
1107        pane_item_status
1108    }
1109
1110    pub(crate) fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1111        if self._schedule_serialize.is_none() {
1112            self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
1113                cx.background_executor()
1114                    .timer(Duration::from_millis(100))
1115                    .await;
1116
1117                let Some((adapter_name, pane_layout)) = this
1118                    .read_with(cx, |this, cx| {
1119                        let adapter_name = this.session.read(cx).adapter();
1120                        (
1121                            adapter_name,
1122                            persistence::build_serialized_layout(
1123                                &this.panes.root,
1124                                this.dock_axis,
1125                                cx,
1126                            ),
1127                        )
1128                    })
1129                    .ok()
1130                else {
1131                    return;
1132                };
1133
1134                persistence::serialize_pane_layout(adapter_name, pane_layout)
1135                    .await
1136                    .log_err();
1137
1138                this.update(cx, |this, _| {
1139                    this._schedule_serialize.take();
1140                })
1141                .ok();
1142            }));
1143        }
1144    }
1145
1146    pub(crate) fn handle_pane_event(
1147        this: &mut RunningState,
1148        source_pane: &Entity<Pane>,
1149        event: &Event,
1150        window: &mut Window,
1151        cx: &mut Context<RunningState>,
1152    ) {
1153        this.serialize_layout(window, cx);
1154        match event {
1155            Event::Remove { .. } => {
1156                let _did_find_pane = this.panes.remove(&source_pane).is_ok();
1157                debug_assert!(_did_find_pane);
1158                cx.notify();
1159            }
1160            Event::Focus => {
1161                this.active_pane = Some(source_pane.clone());
1162            }
1163            Event::ZoomIn => {
1164                source_pane.update(cx, |pane, cx| {
1165                    pane.set_zoomed(true, cx);
1166                });
1167                cx.notify();
1168            }
1169            Event::ZoomOut => {
1170                source_pane.update(cx, |pane, cx| {
1171                    pane.set_zoomed(false, cx);
1172                });
1173                cx.notify();
1174            }
1175            _ => {}
1176        }
1177    }
1178
1179    pub(crate) fn activate_pane_in_direction(
1180        &mut self,
1181        direction: SplitDirection,
1182        window: &mut Window,
1183        cx: &mut Context<Self>,
1184    ) {
1185        if let Some(pane) = self
1186            .active_pane
1187            .as_ref()
1188            .and_then(|pane| self.panes.find_pane_in_direction(pane, direction, cx))
1189        {
1190            window.focus(&pane.focus_handle(cx));
1191        } else {
1192            self.workspace
1193                .update(cx, |workspace, cx| {
1194                    workspace.activate_pane_in_direction(direction, window, cx)
1195                })
1196                .ok();
1197        }
1198    }
1199
1200    pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) {
1201        if self.thread_id.is_some() {
1202            self.stack_frame_list
1203                .update(cx, |list, cx| list.go_to_selected_stack_frame(window, cx));
1204        }
1205    }
1206
1207    pub(crate) fn has_open_context_menu(&self, cx: &App) -> bool {
1208        self.variable_list.read(cx).has_open_context_menu()
1209    }
1210
1211    pub fn session(&self) -> &Entity<Session> {
1212        &self.session
1213    }
1214
1215    pub fn session_id(&self) -> SessionId {
1216        self.session_id
1217    }
1218
1219    pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option<dap::StackFrameId> {
1220        self.stack_frame_list.read(cx).selected_stack_frame_id()
1221    }
1222
1223    #[cfg(test)]
1224    pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
1225        &self.stack_frame_list
1226    }
1227
1228    #[cfg(test)]
1229    pub fn console(&self) -> &Entity<Console> {
1230        &self.console
1231    }
1232
1233    #[cfg(test)]
1234    pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
1235        &self.module_list
1236    }
1237
1238    pub(crate) fn activate_item(&self, item: DebuggerPaneItem, window: &mut Window, cx: &mut App) {
1239        let (variable_list_position, pane) = self
1240            .panes
1241            .panes()
1242            .into_iter()
1243            .find_map(|pane| {
1244                pane.read(cx)
1245                    .items_of_type::<SubView>()
1246                    .position(|view| view.read(cx).view_kind() == item)
1247                    .map(|view| (view, pane))
1248            })
1249            .unwrap();
1250        pane.update(cx, |this, cx| {
1251            this.activate_item(variable_list_position, true, true, window, cx);
1252        })
1253    }
1254
1255    #[cfg(test)]
1256    pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
1257        &self.variable_list
1258    }
1259
1260    #[cfg(test)]
1261    pub(crate) fn serialized_layout(&self, cx: &App) -> SerializedLayout {
1262        persistence::build_serialized_layout(&self.panes.root, self.dock_axis, cx)
1263    }
1264
1265    pub fn capabilities(&self, cx: &App) -> Capabilities {
1266        self.session().read(cx).capabilities().clone()
1267    }
1268
1269    pub fn select_current_thread(
1270        &mut self,
1271        threads: &Vec<(Thread, ThreadStatus)>,
1272        window: &mut Window,
1273        cx: &mut Context<Self>,
1274    ) {
1275        let selected_thread = self
1276            .thread_id
1277            .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0))
1278            .or_else(|| threads.first());
1279
1280        let Some((selected_thread, _)) = selected_thread else {
1281            return;
1282        };
1283
1284        if Some(ThreadId(selected_thread.id)) != self.thread_id {
1285            self.select_thread(ThreadId(selected_thread.id), window, cx);
1286        }
1287    }
1288
1289    pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
1290        self.thread_id
1291    }
1292
1293    pub fn thread_status(&self, cx: &App) -> Option<ThreadStatus> {
1294        self.thread_id
1295            .map(|id| self.session().read(cx).thread_status(id))
1296    }
1297
1298    fn select_thread(&mut self, thread_id: ThreadId, window: &mut Window, cx: &mut Context<Self>) {
1299        if self.thread_id.is_some_and(|id| id == thread_id) {
1300            return;
1301        }
1302
1303        self.thread_id = Some(thread_id);
1304
1305        self.stack_frame_list
1306            .update(cx, |list, cx| list.schedule_refresh(true, window, cx));
1307    }
1308
1309    pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
1310        let Some(thread_id) = self.thread_id else {
1311            return;
1312        };
1313
1314        self.session().update(cx, |state, cx| {
1315            state.continue_thread(thread_id, cx);
1316        });
1317    }
1318
1319    pub fn step_over(&mut self, cx: &mut Context<Self>) {
1320        let Some(thread_id) = self.thread_id else {
1321            return;
1322        };
1323
1324        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1325
1326        self.session().update(cx, |state, cx| {
1327            state.step_over(thread_id, granularity, cx);
1328        });
1329    }
1330
1331    pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
1332        let Some(thread_id) = self.thread_id else {
1333            return;
1334        };
1335
1336        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1337
1338        self.session().update(cx, |state, cx| {
1339            state.step_in(thread_id, granularity, cx);
1340        });
1341    }
1342
1343    pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
1344        let Some(thread_id) = self.thread_id else {
1345            return;
1346        };
1347
1348        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1349
1350        self.session().update(cx, |state, cx| {
1351            state.step_out(thread_id, granularity, cx);
1352        });
1353    }
1354
1355    pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
1356        let Some(thread_id) = self.thread_id else {
1357            return;
1358        };
1359
1360        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1361
1362        self.session().update(cx, |state, cx| {
1363            state.step_back(thread_id, granularity, cx);
1364        });
1365    }
1366
1367    pub fn restart_session(&self, cx: &mut Context<Self>) {
1368        self.session().update(cx, |state, cx| {
1369            state.restart(None, cx);
1370        });
1371    }
1372
1373    pub fn pause_thread(&self, cx: &mut Context<Self>) {
1374        let Some(thread_id) = self.thread_id else {
1375            return;
1376        };
1377
1378        self.session().update(cx, |state, cx| {
1379            state.pause_thread(thread_id, cx);
1380        });
1381    }
1382
1383    pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
1384        self.workspace
1385            .update(cx, |workspace, cx| {
1386                workspace
1387                    .project()
1388                    .read(cx)
1389                    .breakpoint_store()
1390                    .update(cx, |store, cx| {
1391                        store.remove_active_position(Some(self.session_id), cx)
1392                    })
1393            })
1394            .log_err();
1395
1396        self.session.update(cx, |session, cx| {
1397            session.shutdown(cx).detach();
1398        })
1399    }
1400
1401    pub fn stop_thread(&self, cx: &mut Context<Self>) {
1402        let Some(thread_id) = self.thread_id else {
1403            return;
1404        };
1405
1406        self.workspace
1407            .update(cx, |workspace, cx| {
1408                workspace
1409                    .project()
1410                    .read(cx)
1411                    .breakpoint_store()
1412                    .update(cx, |store, cx| {
1413                        store.remove_active_position(Some(self.session_id), cx)
1414                    })
1415            })
1416            .log_err();
1417
1418        self.session().update(cx, |state, cx| {
1419            state.terminate_threads(Some(vec![thread_id; 1]), cx);
1420        });
1421    }
1422
1423    #[expect(
1424        unused,
1425        reason = "Support for disconnecting a client is not wired through yet"
1426    )]
1427    pub fn disconnect_client(&self, cx: &mut Context<Self>) {
1428        self.session().update(cx, |state, cx| {
1429            state.disconnect_client(cx);
1430        });
1431    }
1432
1433    pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context<Self>) {
1434        self.session.update(cx, |session, cx| {
1435            session.toggle_ignore_breakpoints(cx).detach();
1436        });
1437    }
1438
1439    pub(crate) fn thread_dropdown(
1440        &self,
1441        window: &mut Window,
1442        cx: &mut Context<'_, RunningState>,
1443    ) -> DropdownMenu {
1444        let state = cx.entity();
1445        let threads = self.session.update(cx, |this, cx| this.threads(cx));
1446        let selected_thread_name = threads
1447            .iter()
1448            .find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
1449            .map(|(thread, _)| thread.name.clone())
1450            .unwrap_or("Threads".to_owned());
1451        DropdownMenu::new(
1452            ("thread-list", self.session_id.0),
1453            selected_thread_name,
1454            ContextMenu::build_eager(window, cx, move |mut this, _, _| {
1455                for (thread, _) in threads {
1456                    let state = state.clone();
1457                    let thread_id = thread.id;
1458                    this = this.entry(thread.name, None, move |window, cx| {
1459                        state.update(cx, |state, cx| {
1460                            state.select_thread(ThreadId(thread_id), window, cx);
1461                        });
1462                    });
1463                }
1464                this
1465            }),
1466        )
1467    }
1468
1469    fn default_pane_layout(
1470        project: Entity<Project>,
1471        workspace: &WeakEntity<Workspace>,
1472        stack_frame_list: &Entity<StackFrameList>,
1473        variable_list: &Entity<VariableList>,
1474        module_list: &Entity<ModuleList>,
1475        loaded_source_list: &Entity<LoadedSourceList>,
1476        console: &Entity<Console>,
1477        breakpoints: &Entity<BreakpointList>,
1478        dock_axis: Axis,
1479        subscriptions: &mut HashMap<EntityId, Subscription>,
1480        window: &mut Window,
1481        cx: &mut Context<'_, RunningState>,
1482    ) -> Member {
1483        let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1484        leftmost_pane.update(cx, |this, cx| {
1485            this.add_item(
1486                Box::new(SubView::new(
1487                    this.focus_handle(cx),
1488                    stack_frame_list.clone().into(),
1489                    DebuggerPaneItem::Frames,
1490                    None,
1491                    cx,
1492                )),
1493                true,
1494                false,
1495                None,
1496                window,
1497                cx,
1498            );
1499            this.add_item(
1500                Box::new(SubView::new(
1501                    breakpoints.focus_handle(cx),
1502                    breakpoints.clone().into(),
1503                    DebuggerPaneItem::BreakpointList,
1504                    None,
1505                    cx,
1506                )),
1507                true,
1508                false,
1509                None,
1510                window,
1511                cx,
1512            );
1513            this.activate_item(0, false, false, window, cx);
1514        });
1515        let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1516
1517        center_pane.update(cx, |this, cx| {
1518            this.add_item(
1519                Box::new(SubView::new(
1520                    variable_list.focus_handle(cx),
1521                    variable_list.clone().into(),
1522                    DebuggerPaneItem::Variables,
1523                    None,
1524                    cx,
1525                )),
1526                true,
1527                false,
1528                None,
1529                window,
1530                cx,
1531            );
1532            this.add_item(
1533                Box::new(SubView::new(
1534                    module_list.focus_handle(cx),
1535                    module_list.clone().into(),
1536                    DebuggerPaneItem::Modules,
1537                    None,
1538                    cx,
1539                )),
1540                false,
1541                false,
1542                None,
1543                window,
1544                cx,
1545            );
1546
1547            this.add_item(
1548                Box::new(SubView::new(
1549                    loaded_source_list.focus_handle(cx),
1550                    loaded_source_list.clone().into(),
1551                    DebuggerPaneItem::LoadedSources,
1552                    None,
1553                    cx,
1554                )),
1555                false,
1556                false,
1557                None,
1558                window,
1559                cx,
1560            );
1561            this.activate_item(0, false, false, window, cx);
1562        });
1563
1564        let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1565        rightmost_pane.update(cx, |this, cx| {
1566            let weak_console = console.downgrade();
1567            this.add_item(
1568                Box::new(SubView::new(
1569                    this.focus_handle(cx),
1570                    console.clone().into(),
1571                    DebuggerPaneItem::Console,
1572                    Some(Box::new(move |cx| {
1573                        weak_console
1574                            .read_with(cx, |console, cx| console.show_indicator(cx))
1575                            .unwrap_or_default()
1576                    })),
1577                    cx,
1578                )),
1579                true,
1580                false,
1581                None,
1582                window,
1583                cx,
1584            );
1585        });
1586
1587        subscriptions.extend(
1588            [&leftmost_pane, &center_pane, &rightmost_pane]
1589                .into_iter()
1590                .map(|entity| {
1591                    (
1592                        entity.entity_id(),
1593                        cx.subscribe_in(entity, window, Self::handle_pane_event),
1594                    )
1595                }),
1596        );
1597
1598        let group_root = workspace::PaneAxis::new(
1599            dock_axis.invert(),
1600            [leftmost_pane, center_pane, rightmost_pane]
1601                .into_iter()
1602                .map(workspace::Member::Pane)
1603                .collect(),
1604        );
1605
1606        Member::Axis(group_root)
1607    }
1608
1609    pub(crate) fn invert_axies(&mut self) {
1610        self.dock_axis = self.dock_axis.invert();
1611        self.panes.invert_axies();
1612    }
1613}
1614
1615impl EventEmitter<DebugPanelItemEvent> for RunningState {}
1616
1617impl Focusable for RunningState {
1618    fn focus_handle(&self, _: &App) -> FocusHandle {
1619        self.focus_handle.clone()
1620    }
1621}