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