running.rs

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