running.rs

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