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