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