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