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