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                let terminal = project
1149                    .update(cx, |project, cx| {
1150                        project.create_terminal_task(
1151                            task_with_shell.clone(),
1152                            cx,
1153                        )
1154                    }).await?;
1155
1156                let terminal_view = cx.new_window_entity(|window, cx| {
1157                    TerminalView::new(
1158                        terminal.clone(),
1159                        weak_workspace,
1160                        None,
1161                        weak_project,
1162                        window,
1163                        cx,
1164                    )
1165                })?;
1166
1167                this.update_in(cx, |this, window, cx| {
1168                    this.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
1169                    this.debug_terminal.update(cx, |debug_terminal, cx| {
1170                        debug_terminal.terminal = Some(terminal_view);
1171                        cx.notify();
1172                    });
1173                })?;
1174
1175                let exit_status = terminal
1176                    .read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))
1177                    .await
1178                    .context("Failed to wait for completed task")?;
1179
1180                if !exit_status.success() {
1181                    anyhow::bail!("Build failed");
1182                }
1183                Some((task.resolved.clone(), locator_name, extra_config))
1184            } else {
1185                None
1186            };
1187
1188            if config_is_valid {
1189            } else if let Some((task, locator_name, extra_config)) = build_output {
1190                let locator_name =
1191                    locator_name.with_context(|| {
1192                        format!("Could not find a valid locator for a build task and configure is invalid with error: {}", request_type.err()
1193                            .map(|err| err.to_string())
1194                            .unwrap_or_default())
1195                    })?;
1196                let request = dap_store
1197                    .update(cx, |this, cx| {
1198                        this.run_debug_locator(&locator_name, task, cx)
1199                    })?
1200                    .await?;
1201
1202                let zed_config = ZedDebugConfig {
1203                    label: label.clone(),
1204                    adapter: adapter.clone(),
1205                    request,
1206                    stop_on_entry: None,
1207                };
1208
1209                let scenario = dap_registry
1210                    .adapter(&adapter)
1211                    .with_context(|| anyhow!("{}: is not a valid adapter name", &adapter))?.config_from_zed_format(zed_config)
1212                    .await?;
1213                config = scenario.config;
1214                util::merge_non_null_json_value_into(extra_config, &mut config);
1215
1216                Self::substitute_variables_in_config(&mut config, &task_context);
1217            } else {
1218                let Err(e) = request_type else {
1219                    unreachable!();
1220                };
1221                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}");
1222            };
1223
1224            Ok(DebugTaskDefinition {
1225                label,
1226                adapter: DebugAdapterName(adapter),
1227                config,
1228                tcp_connection,
1229            })
1230        })
1231    }
1232
1233    fn handle_run_in_terminal(
1234        &self,
1235        request: &RunInTerminalRequestArguments,
1236        mut sender: mpsc::Sender<Result<u32>>,
1237        window: &mut Window,
1238        cx: &mut Context<Self>,
1239    ) -> Task<Result<()>> {
1240        let running = cx.entity();
1241        let Ok(project) = self
1242            .workspace
1243            .read_with(cx, |workspace, _| workspace.project().clone())
1244        else {
1245            return Task::ready(Err(anyhow!("no workspace")));
1246        };
1247        let session = self.session.read(cx);
1248
1249        let cwd = (!request.cwd.is_empty())
1250            .then(|| PathBuf::from(&request.cwd))
1251            .or_else(|| session.binary().unwrap().cwd.clone());
1252
1253        let mut envs: HashMap<String, String> =
1254            self.session.read(cx).task_context().project_env.clone();
1255        if let Some(Value::Object(env)) = &request.env {
1256            for (key, value) in env {
1257                let value_str = match (key.as_str(), value) {
1258                    (_, Value::String(value)) => value,
1259                    _ => continue,
1260                };
1261
1262                envs.insert(key.clone(), value_str.clone());
1263            }
1264        }
1265
1266        let mut args = request.args.clone();
1267        let command = if envs.contains_key("VSCODE_INSPECTOR_OPTIONS") {
1268            // Handle special case for NodeJS debug adapter
1269            // If the Node binary path is provided (possibly with arguments like --experimental-network-inspection),
1270            // we set the command to None
1271            // This prevents the NodeJS REPL from appearing, which is not the desired behavior
1272            // The expected usage is for users to provide their own Node command, e.g., `node test.js`
1273            // This allows the NodeJS debug client to attach correctly
1274            if args
1275                .iter()
1276                .filter(|arg| !arg.starts_with("--"))
1277                .collect::<Vec<_>>()
1278                .len()
1279                > 1
1280            {
1281                Some(args.remove(0))
1282            } else {
1283                None
1284            }
1285        } else if !args.is_empty() {
1286            Some(args.remove(0))
1287        } else {
1288            None
1289        };
1290
1291        let shell = project.read(cx).terminal_settings(&cwd, cx).shell.clone();
1292        let title = request
1293            .title
1294            .clone()
1295            .filter(|title| !title.is_empty())
1296            .or_else(|| command.clone())
1297            .unwrap_or_else(|| "Debug terminal".to_string());
1298        let kind = task::SpawnInTerminal {
1299            id: task::TaskId("debug".to_string()),
1300            full_label: title.clone(),
1301            label: title.clone(),
1302            command,
1303            args,
1304            command_label: title,
1305            cwd,
1306            env: envs,
1307            use_new_terminal: true,
1308            allow_concurrent_runs: true,
1309            reveal: task::RevealStrategy::NoFocus,
1310            reveal_target: task::RevealTarget::Dock,
1311            hide: task::HideStrategy::Never,
1312            shell,
1313            show_summary: false,
1314            show_command: false,
1315            show_rerun: false,
1316        };
1317
1318        let workspace = self.workspace.clone();
1319        let weak_project = project.downgrade();
1320
1321        let terminal_task =
1322            project.update(cx, |project, cx| project.create_terminal_task(kind, cx));
1323        let terminal_task = cx.spawn_in(window, async move |_, cx| {
1324            let terminal = terminal_task.await?;
1325
1326            let terminal_view = cx.new_window_entity(|window, cx| {
1327                TerminalView::new(terminal.clone(), workspace, None, weak_project, window, cx)
1328            })?;
1329
1330            running.update_in(cx, |running, window, cx| {
1331                running.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
1332                running.debug_terminal.update(cx, |debug_terminal, cx| {
1333                    debug_terminal.terminal = Some(terminal_view);
1334                    cx.notify();
1335                });
1336            })?;
1337
1338            terminal.read_with(cx, |terminal, _| {
1339                terminal
1340                    .pid()
1341                    .map(|pid| pid.as_u32())
1342                    .context("Terminal was spawned but PID was not available")
1343            })
1344        });
1345
1346        cx.background_spawn(async move { anyhow::Ok(sender.send(terminal_task.await).await?) })
1347    }
1348
1349    fn create_sub_view(
1350        &self,
1351        item_kind: DebuggerPaneItem,
1352        pane: &Entity<Pane>,
1353        cx: &mut Context<Self>,
1354    ) -> Box<dyn ItemHandle> {
1355        let running_state = cx.weak_entity();
1356        let host_pane = pane.downgrade();
1357
1358        match item_kind {
1359            DebuggerPaneItem::Console => Box::new(SubView::console(
1360                self.console.clone(),
1361                running_state,
1362                host_pane,
1363                cx,
1364            )),
1365            DebuggerPaneItem::Variables => Box::new(SubView::new(
1366                self.variable_list.focus_handle(cx),
1367                self.variable_list.clone().into(),
1368                item_kind,
1369                running_state,
1370                host_pane,
1371                cx,
1372            )),
1373            DebuggerPaneItem::BreakpointList => Box::new(SubView::breakpoint_list(
1374                self.breakpoint_list.clone(),
1375                running_state,
1376                host_pane,
1377                cx,
1378            )),
1379            DebuggerPaneItem::Frames => Box::new(SubView::new(
1380                self.stack_frame_list.focus_handle(cx),
1381                self.stack_frame_list.clone().into(),
1382                item_kind,
1383                running_state,
1384                host_pane,
1385                cx,
1386            )),
1387            DebuggerPaneItem::Modules => Box::new(SubView::new(
1388                self.module_list.focus_handle(cx),
1389                self.module_list.clone().into(),
1390                item_kind,
1391                running_state,
1392                host_pane,
1393                cx,
1394            )),
1395            DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
1396                self.loaded_sources_list.focus_handle(cx),
1397                self.loaded_sources_list.clone().into(),
1398                item_kind,
1399                running_state,
1400                host_pane,
1401                cx,
1402            )),
1403            DebuggerPaneItem::Terminal => Box::new(SubView::new(
1404                self.debug_terminal.focus_handle(cx),
1405                self.debug_terminal.clone().into(),
1406                item_kind,
1407                running_state,
1408                host_pane,
1409                cx,
1410            )),
1411            DebuggerPaneItem::MemoryView => Box::new(SubView::new(
1412                self.memory_view.focus_handle(cx),
1413                self.memory_view.clone().into(),
1414                item_kind,
1415                running_state,
1416                host_pane,
1417                cx,
1418            )),
1419        }
1420    }
1421
1422    pub(crate) fn ensure_pane_item(
1423        &mut self,
1424        item_kind: DebuggerPaneItem,
1425        window: &mut Window,
1426        cx: &mut Context<Self>,
1427    ) {
1428        if self.pane_items_status(cx).get(&item_kind) == Some(&true) {
1429            return;
1430        };
1431        let pane = self.panes.last_pane();
1432        let sub_view = self.create_sub_view(item_kind, &pane, cx);
1433
1434        pane.update(cx, |pane, cx| {
1435            pane.add_item_inner(sub_view, false, false, false, None, window, cx);
1436        })
1437    }
1438
1439    pub(crate) fn add_pane_item(
1440        &mut self,
1441        item_kind: DebuggerPaneItem,
1442        position: Point<Pixels>,
1443        window: &mut Window,
1444        cx: &mut Context<Self>,
1445    ) {
1446        debug_assert!(
1447            item_kind.is_supported(self.session.read(cx).capabilities()),
1448            "We should only allow adding supported item kinds"
1449        );
1450
1451        if let Some(pane) = self.panes.pane_at_pixel_position(position) {
1452            let sub_view = self.create_sub_view(item_kind, pane, cx);
1453
1454            pane.update(cx, |pane, cx| {
1455                pane.add_item(sub_view, false, false, None, window, cx);
1456            })
1457        }
1458    }
1459
1460    pub(crate) fn pane_items_status(&self, cx: &App) -> IndexMap<DebuggerPaneItem, bool> {
1461        let caps = self.session.read(cx).capabilities();
1462        let mut pane_item_status = IndexMap::from_iter(
1463            DebuggerPaneItem::all()
1464                .iter()
1465                .filter(|kind| kind.is_supported(caps))
1466                .map(|kind| (*kind, false)),
1467        );
1468        self.panes.panes().iter().for_each(|pane| {
1469            pane.read(cx)
1470                .items()
1471                .filter_map(|item| item.act_as::<SubView>(cx))
1472                .for_each(|view| {
1473                    pane_item_status.insert(view.read(cx).kind, true);
1474                });
1475        });
1476
1477        pane_item_status
1478    }
1479
1480    pub(crate) fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1481        if self._schedule_serialize.is_none() {
1482            self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
1483                cx.background_executor()
1484                    .timer(Duration::from_millis(100))
1485                    .await;
1486
1487                let Some((adapter_name, pane_layout)) = this
1488                    .read_with(cx, |this, cx| {
1489                        let adapter_name = this.session.read(cx).adapter();
1490                        (
1491                            adapter_name,
1492                            persistence::build_serialized_layout(
1493                                &this.panes.root,
1494                                this.dock_axis,
1495                                cx,
1496                            ),
1497                        )
1498                    })
1499                    .ok()
1500                else {
1501                    return;
1502                };
1503
1504                persistence::serialize_pane_layout(adapter_name, pane_layout)
1505                    .await
1506                    .log_err();
1507
1508                this.update(cx, |this, _| {
1509                    this._schedule_serialize.take();
1510                })
1511                .ok();
1512            }));
1513        }
1514    }
1515
1516    pub(crate) fn handle_pane_event(
1517        this: &mut RunningState,
1518        source_pane: &Entity<Pane>,
1519        event: &Event,
1520        window: &mut Window,
1521        cx: &mut Context<RunningState>,
1522    ) {
1523        this.serialize_layout(window, cx);
1524        match event {
1525            Event::AddItem { item } => {
1526                if let Some(sub_view) = item.downcast::<SubView>() {
1527                    sub_view.update(cx, |sub_view, _| {
1528                        sub_view.set_host_pane(source_pane.downgrade());
1529                    });
1530                }
1531            }
1532            Event::Remove { .. } => {
1533                let _did_find_pane = this.panes.remove(source_pane, cx).is_ok();
1534                debug_assert!(_did_find_pane);
1535                cx.notify();
1536            }
1537            Event::Focus => {
1538                this.active_pane = source_pane.clone();
1539            }
1540            _ => {}
1541        }
1542    }
1543
1544    pub(crate) fn activate_pane_in_direction(
1545        &mut self,
1546        direction: SplitDirection,
1547        window: &mut Window,
1548        cx: &mut Context<Self>,
1549    ) {
1550        let active_pane = self.active_pane.clone();
1551        if let Some(pane) = self
1552            .panes
1553            .find_pane_in_direction(&active_pane, direction, cx)
1554        {
1555            pane.update(cx, |pane, cx| {
1556                pane.focus_active_item(window, cx);
1557            })
1558        } else {
1559            self.workspace
1560                .update(cx, |workspace, cx| {
1561                    workspace.activate_pane_in_direction(direction, window, cx)
1562                })
1563                .ok();
1564        }
1565    }
1566
1567    pub(crate) fn go_to_selected_stack_frame(&self, window: &mut Window, cx: &mut Context<Self>) {
1568        if self.thread_id.is_some() {
1569            self.stack_frame_list
1570                .update(cx, |list, cx| {
1571                    let Some(stack_frame_id) = list.opened_stack_frame_id() else {
1572                        return Task::ready(Ok(()));
1573                    };
1574                    list.go_to_stack_frame(stack_frame_id, window, cx)
1575                })
1576                .detach();
1577        }
1578    }
1579
1580    pub(crate) fn has_open_context_menu(&self, cx: &App) -> bool {
1581        self.variable_list.read(cx).has_open_context_menu()
1582    }
1583
1584    pub fn session(&self) -> &Entity<Session> {
1585        &self.session
1586    }
1587
1588    pub fn session_id(&self) -> SessionId {
1589        self.session_id
1590    }
1591
1592    pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option<dap::StackFrameId> {
1593        self.stack_frame_list.read(cx).opened_stack_frame_id()
1594    }
1595
1596    pub(crate) fn stack_frame_list(&self) -> &Entity<StackFrameList> {
1597        &self.stack_frame_list
1598    }
1599
1600    #[cfg(test)]
1601    pub fn console(&self) -> &Entity<Console> {
1602        &self.console
1603    }
1604
1605    #[cfg(test)]
1606    pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
1607        &self.module_list
1608    }
1609
1610    pub(crate) fn activate_item(
1611        &mut self,
1612        item: DebuggerPaneItem,
1613        window: &mut Window,
1614        cx: &mut Context<Self>,
1615    ) {
1616        self.ensure_pane_item(item, window, cx);
1617
1618        let (variable_list_position, pane) = self
1619            .panes
1620            .panes()
1621            .into_iter()
1622            .find_map(|pane| {
1623                pane.read(cx)
1624                    .items_of_type::<SubView>()
1625                    .position(|view| view.read(cx).view_kind() == item)
1626                    .map(|view| (view, pane))
1627            })
1628            .unwrap();
1629
1630        pane.update(cx, |this, cx| {
1631            this.activate_item(variable_list_position, true, true, window, cx);
1632        });
1633    }
1634
1635    #[cfg(test)]
1636    pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
1637        &self.variable_list
1638    }
1639
1640    #[cfg(test)]
1641    pub(crate) fn serialized_layout(&self, cx: &App) -> SerializedLayout {
1642        persistence::build_serialized_layout(&self.panes.root, self.dock_axis, cx)
1643    }
1644
1645    pub fn capabilities(&self, cx: &App) -> Capabilities {
1646        self.session().read(cx).capabilities().clone()
1647    }
1648
1649    pub fn select_current_thread(
1650        &mut self,
1651        threads: &Vec<(Thread, ThreadStatus)>,
1652        window: &mut Window,
1653        cx: &mut Context<Self>,
1654    ) {
1655        let selected_thread = self
1656            .thread_id
1657            .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0))
1658            .or_else(|| threads.first());
1659
1660        let Some((selected_thread, _)) = selected_thread else {
1661            return;
1662        };
1663
1664        if Some(ThreadId(selected_thread.id)) != self.thread_id {
1665            self.select_thread(ThreadId(selected_thread.id), window, cx);
1666        }
1667    }
1668
1669    pub fn selected_thread_id(&self) -> Option<ThreadId> {
1670        self.thread_id
1671    }
1672
1673    pub fn thread_status(&self, cx: &App) -> Option<ThreadStatus> {
1674        self.thread_id
1675            .map(|id| self.session().read(cx).thread_status(id))
1676    }
1677
1678    pub(crate) fn select_thread(
1679        &mut self,
1680        thread_id: ThreadId,
1681        window: &mut Window,
1682        cx: &mut Context<Self>,
1683    ) {
1684        if self.thread_id.is_some_and(|id| id == thread_id) {
1685            return;
1686        }
1687
1688        self.thread_id = Some(thread_id);
1689
1690        self.stack_frame_list
1691            .update(cx, |list, cx| list.schedule_refresh(true, window, cx));
1692    }
1693
1694    pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
1695        let Some(thread_id) = self.thread_id else {
1696            return;
1697        };
1698
1699        self.session().update(cx, |state, cx| {
1700            state.continue_thread(thread_id, cx);
1701        });
1702    }
1703
1704    pub fn step_over(&mut self, cx: &mut Context<Self>) {
1705        let Some(thread_id) = self.thread_id else {
1706            return;
1707        };
1708
1709        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1710
1711        self.session().update(cx, |state, cx| {
1712            state.step_over(thread_id, granularity, cx);
1713        });
1714    }
1715
1716    pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
1717        let Some(thread_id) = self.thread_id else {
1718            return;
1719        };
1720
1721        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1722
1723        self.session().update(cx, |state, cx| {
1724            state.step_in(thread_id, granularity, cx);
1725        });
1726    }
1727
1728    pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
1729        let Some(thread_id) = self.thread_id else {
1730            return;
1731        };
1732
1733        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1734
1735        self.session().update(cx, |state, cx| {
1736            state.step_out(thread_id, granularity, cx);
1737        });
1738    }
1739
1740    pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
1741        let Some(thread_id) = self.thread_id else {
1742            return;
1743        };
1744
1745        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1746
1747        self.session().update(cx, |state, cx| {
1748            state.step_back(thread_id, granularity, cx);
1749        });
1750    }
1751
1752    pub fn rerun_session(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1753        if let Some((scenario, context)) = self.scenario.take().zip(self.scenario_context.take())
1754            && scenario.build.is_some()
1755        {
1756            let DebugScenarioContext {
1757                task_context,
1758                active_buffer,
1759                worktree_id,
1760            } = context;
1761            let active_buffer = active_buffer.and_then(|buffer| buffer.upgrade());
1762
1763            self.workspace
1764                .update(cx, |workspace, cx| {
1765                    workspace.start_debug_session(
1766                        scenario,
1767                        task_context,
1768                        active_buffer,
1769                        worktree_id,
1770                        window,
1771                        cx,
1772                    )
1773                })
1774                .ok();
1775        } else {
1776            self.restart_session(cx);
1777        }
1778    }
1779
1780    pub fn restart_session(&self, cx: &mut Context<Self>) {
1781        self.session().update(cx, |state, cx| {
1782            state.restart(None, cx);
1783        });
1784    }
1785
1786    pub fn pause_thread(&self, cx: &mut Context<Self>) {
1787        let Some(thread_id) = self.thread_id else {
1788            return;
1789        };
1790
1791        self.session().update(cx, |state, cx| {
1792            state.pause_thread(thread_id, cx);
1793        });
1794    }
1795
1796    pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
1797        self.workspace
1798            .update(cx, |workspace, cx| {
1799                workspace
1800                    .project()
1801                    .read(cx)
1802                    .breakpoint_store()
1803                    .update(cx, |store, cx| {
1804                        store.remove_active_position(Some(self.session_id), cx)
1805                    })
1806            })
1807            .log_err();
1808
1809        let is_building = self.session.update(cx, |session, cx| {
1810            session.shutdown(cx).detach();
1811            matches!(session.state, session::SessionState::Booting(_))
1812        });
1813
1814        if is_building {
1815            self.debug_terminal.update(cx, |terminal, cx| {
1816                if let Some(view) = terminal.terminal.as_ref() {
1817                    view.update(cx, |view, cx| {
1818                        view.terminal()
1819                            .update(cx, |terminal, _| terminal.kill_active_task())
1820                    })
1821                }
1822            })
1823        }
1824    }
1825
1826    pub fn stop_thread(&self, cx: &mut Context<Self>) {
1827        let Some(thread_id) = self.thread_id else {
1828            return;
1829        };
1830
1831        self.workspace
1832            .update(cx, |workspace, cx| {
1833                workspace
1834                    .project()
1835                    .read(cx)
1836                    .breakpoint_store()
1837                    .update(cx, |store, cx| {
1838                        store.remove_active_position(Some(self.session_id), cx)
1839                    })
1840            })
1841            .log_err();
1842
1843        self.session().update(cx, |state, cx| {
1844            state.terminate_threads(Some(vec![thread_id; 1]), cx);
1845        });
1846    }
1847
1848    pub fn detach_client(&self, cx: &mut Context<Self>) {
1849        self.session().update(cx, |state, cx| {
1850            state.disconnect_client(cx);
1851        });
1852    }
1853
1854    pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context<Self>) {
1855        self.session.update(cx, |session, cx| {
1856            session.toggle_ignore_breakpoints(cx).detach();
1857        });
1858    }
1859
1860    fn default_pane_layout(
1861        project: Entity<Project>,
1862        workspace: &WeakEntity<Workspace>,
1863        stack_frame_list: &Entity<StackFrameList>,
1864        variable_list: &Entity<VariableList>,
1865        console: &Entity<Console>,
1866        breakpoints: &Entity<BreakpointList>,
1867        debug_terminal: &Entity<DebugTerminal>,
1868        dock_axis: Axis,
1869        subscriptions: &mut HashMap<EntityId, Subscription>,
1870        window: &mut Window,
1871        cx: &mut Context<'_, RunningState>,
1872    ) -> Member {
1873        let running_state = cx.weak_entity();
1874
1875        let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1876        let leftmost_pane_handle = leftmost_pane.downgrade();
1877        let leftmost_frames = SubView::new(
1878            stack_frame_list.focus_handle(cx),
1879            stack_frame_list.clone().into(),
1880            DebuggerPaneItem::Frames,
1881            running_state.clone(),
1882            leftmost_pane_handle.clone(),
1883            cx,
1884        );
1885        let leftmost_breakpoints = SubView::breakpoint_list(
1886            breakpoints.clone(),
1887            running_state.clone(),
1888            leftmost_pane_handle,
1889            cx,
1890        );
1891        leftmost_pane.update(cx, |this, cx| {
1892            this.add_item(Box::new(leftmost_frames), true, false, None, window, cx);
1893            this.add_item(
1894                Box::new(leftmost_breakpoints),
1895                true,
1896                false,
1897                None,
1898                window,
1899                cx,
1900            );
1901            this.activate_item(0, false, false, window, cx);
1902        });
1903
1904        let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1905        let center_pane_handle = center_pane.downgrade();
1906        let center_console = SubView::console(
1907            console.clone(),
1908            running_state.clone(),
1909            center_pane_handle.clone(),
1910            cx,
1911        );
1912        let center_variables = SubView::new(
1913            variable_list.focus_handle(cx),
1914            variable_list.clone().into(),
1915            DebuggerPaneItem::Variables,
1916            running_state.clone(),
1917            center_pane_handle,
1918            cx,
1919        );
1920
1921        center_pane.update(cx, |this, cx| {
1922            this.add_item(Box::new(center_console), true, false, None, window, cx);
1923
1924            this.add_item(Box::new(center_variables), true, false, None, window, cx);
1925            this.activate_item(0, false, false, window, cx);
1926        });
1927
1928        let rightmost_pane = new_debugger_pane(workspace.clone(), project, window, cx);
1929        let rightmost_terminal = SubView::new(
1930            debug_terminal.focus_handle(cx),
1931            debug_terminal.clone().into(),
1932            DebuggerPaneItem::Terminal,
1933            running_state,
1934            rightmost_pane.downgrade(),
1935            cx,
1936        );
1937        rightmost_pane.update(cx, |this, cx| {
1938            this.add_item(Box::new(rightmost_terminal), false, false, None, window, cx);
1939        });
1940
1941        subscriptions.extend(
1942            [&leftmost_pane, &center_pane, &rightmost_pane]
1943                .into_iter()
1944                .map(|entity| {
1945                    (
1946                        entity.entity_id(),
1947                        cx.subscribe_in(entity, window, Self::handle_pane_event),
1948                    )
1949                }),
1950        );
1951
1952        let group_root = workspace::PaneAxis::new(
1953            dock_axis.invert(),
1954            [leftmost_pane, center_pane, rightmost_pane]
1955                .into_iter()
1956                .map(workspace::Member::Pane)
1957                .collect(),
1958        );
1959
1960        Member::Axis(group_root)
1961    }
1962
1963    pub(crate) fn invert_axies(&mut self, cx: &mut App) {
1964        self.dock_axis = self.dock_axis.invert();
1965        self.panes.invert_axies(cx);
1966    }
1967}
1968
1969impl Focusable for RunningState {
1970    fn focus_handle(&self, _: &App) -> FocusHandle {
1971        self.focus_handle.clone()
1972    }
1973}