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                &console,
 632                &breakpoint_list,
 633                &debug_terminal,
 634                dock_axis,
 635                &mut pane_close_subscriptions,
 636                window,
 637                cx,
 638            );
 639
 640            workspace::PaneGroup::with_root(root)
 641        };
 642
 643        Self {
 644            session,
 645            workspace,
 646            focus_handle,
 647            variable_list,
 648            _subscriptions,
 649            thread_id: None,
 650            _remote_id: None,
 651            stack_frame_list,
 652            session_id,
 653            panes,
 654            active_pane: None,
 655            module_list,
 656            console,
 657            breakpoint_list,
 658            loaded_sources_list: loaded_source_list,
 659            pane_close_subscriptions,
 660            debug_terminal,
 661            dock_axis,
 662            _schedule_serialize: None,
 663        }
 664    }
 665
 666    pub(crate) fn remove_pane_item(
 667        &mut self,
 668        item_kind: DebuggerPaneItem,
 669        window: &mut Window,
 670        cx: &mut Context<Self>,
 671    ) {
 672        if let Some((pane, item_id)) = self.panes.panes().iter().find_map(|pane| {
 673            Some(pane).zip(
 674                pane.read(cx)
 675                    .items()
 676                    .find(|item| {
 677                        item.act_as::<SubView>(cx)
 678                            .is_some_and(|view| view.read(cx).kind == item_kind)
 679                    })
 680                    .map(|item| item.item_id()),
 681            )
 682        }) {
 683            pane.update(cx, |pane, cx| {
 684                pane.remove_item(item_id, false, true, window, cx)
 685            })
 686        }
 687    }
 688
 689    pub(crate) fn has_pane_at_position(&self, position: Point<Pixels>) -> bool {
 690        self.panes.pane_at_pixel_position(position).is_some()
 691    }
 692
 693    pub(crate) fn resolve_scenario(
 694        &self,
 695        scenario: DebugScenario,
 696        task_context: TaskContext,
 697        buffer: Option<Entity<Buffer>>,
 698        worktree_id: Option<WorktreeId>,
 699        window: &Window,
 700        cx: &mut Context<Self>,
 701    ) -> Task<Result<DebugTaskDefinition>> {
 702        let Some(workspace) = self.workspace.upgrade() else {
 703            return Task::ready(Err(anyhow!("no workspace")));
 704        };
 705        let project = workspace.read(cx).project().clone();
 706        let dap_store = project.read(cx).dap_store().downgrade();
 707        let task_store = project.read(cx).task_store().downgrade();
 708        let weak_project = project.downgrade();
 709        let weak_workspace = workspace.downgrade();
 710        let is_local = project.read(cx).is_local();
 711        cx.spawn_in(window, async move |this, cx| {
 712            let DebugScenario {
 713                adapter,
 714                label,
 715                build,
 716                request,
 717                initialize_args,
 718                tcp_connection,
 719                stop_on_entry,
 720            } = scenario;
 721            let build_output = if let Some(build) = build {
 722                let (task, locator_name) = match build {
 723                    BuildTaskDefinition::Template {
 724                        task_template,
 725                        locator_name,
 726                    } => (task_template, locator_name),
 727                    BuildTaskDefinition::ByName(ref label) => {
 728                        let Some(task) = task_store.update(cx, |this, cx| {
 729                            this.task_inventory().and_then(|inventory| {
 730                                inventory.read(cx).task_template_by_label(
 731                                    buffer,
 732                                    worktree_id,
 733                                    &label,
 734                                    cx,
 735                                )
 736                            })
 737                        })?
 738                        else {
 739                            anyhow::bail!("Couldn't find task template for {:?}", build)
 740                        };
 741                        (task, None)
 742                    }
 743                };
 744                let Some(task) = task.resolve_task("debug-build-task", &task_context) else {
 745                    anyhow::bail!("Could not resolve task variables within a debug scenario");
 746                };
 747
 748                let locator_name = if let Some(locator_name) = locator_name {
 749                    debug_assert!(request.is_none());
 750                    Some(locator_name)
 751                } else if request.is_none() {
 752                    dap_store
 753                        .update(cx, |this, cx| {
 754                            this.debug_scenario_for_build_task(
 755                                task.original_task().clone(),
 756                                adapter.clone().into(),
 757                                task.display_label().to_owned().into(),
 758                                cx,
 759                            )
 760                            .and_then(|scenario| {
 761                                match scenario.build {
 762                                    Some(BuildTaskDefinition::Template {
 763                                        locator_name, ..
 764                                    }) => locator_name,
 765                                    _ => None,
 766                                }
 767                            })
 768                        })
 769                        .ok()
 770                        .flatten()
 771                } else {
 772                    None
 773                };
 774
 775                let builder = ShellBuilder::new(is_local, &task.resolved.shell);
 776                let command_label = builder.command_label(&task.resolved.command_label);
 777                let (command, args) =
 778                    builder.build(task.resolved.command.clone(), &task.resolved.args);
 779
 780                let task_with_shell = SpawnInTerminal {
 781                    command_label,
 782                    command,
 783                    args,
 784                    ..task.resolved.clone()
 785                };
 786                let terminal = project
 787                    .update_in(cx, |project, window, cx| {
 788                        project.create_terminal(
 789                            TerminalKind::Task(task_with_shell.clone()),
 790                            window.window_handle(),
 791                            cx,
 792                        )
 793                    })?
 794                    .await?;
 795
 796                let terminal_view = cx.new_window_entity(|window, cx| {
 797                    TerminalView::new(
 798                        terminal.clone(),
 799                        weak_workspace,
 800                        None,
 801                        weak_project,
 802                        false,
 803                        window,
 804                        cx,
 805                    )
 806                })?;
 807
 808                this.update_in(cx, |this, window, cx| {
 809                    this.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
 810                    this.debug_terminal.update(cx, |debug_terminal, cx| {
 811                        debug_terminal.terminal = Some(terminal_view);
 812                        cx.notify();
 813                    });
 814                })?;
 815
 816                let exit_status = terminal
 817                    .read_with(cx, |terminal, cx| terminal.wait_for_completed_task(cx))?
 818                    .await
 819                    .context("Failed to wait for completed task")?;
 820
 821                if !exit_status.success() {
 822                    anyhow::bail!("Build failed");
 823                }
 824                Some((task.resolved.clone(), locator_name))
 825            } else {
 826                None
 827            };
 828            let request = if let Some(request) = request {
 829                request
 830            } else if let Some((task, locator_name)) = build_output {
 831                let locator_name =
 832                    locator_name.context("Could not find a valid locator for a build task")?;
 833                dap_store
 834                    .update(cx, |this, cx| {
 835                        this.run_debug_locator(&locator_name, task, cx)
 836                    })?
 837                    .await?
 838            } else {
 839                anyhow::bail!("No request or build provided");
 840            };
 841            let request = match request {
 842                dap::DebugRequest::Launch(launch_request) => {
 843                    let cwd = match launch_request.cwd.as_deref().and_then(|path| path.to_str()) {
 844                        Some(cwd) => {
 845                            let substituted_cwd = substitute_variables_in_str(&cwd, &task_context)
 846                                .context("substituting variables in cwd")?;
 847                            Some(PathBuf::from(substituted_cwd))
 848                        }
 849                        None => None,
 850                    };
 851
 852                    let env = substitute_variables_in_map(
 853                        &launch_request.env.into_iter().collect(),
 854                        &task_context,
 855                    )
 856                    .context("substituting variables in env")?
 857                    .into_iter()
 858                    .collect();
 859                    let new_launch_request = LaunchRequest {
 860                        program: substitute_variables_in_str(
 861                            &launch_request.program,
 862                            &task_context,
 863                        )
 864                        .context("substituting variables in program")?,
 865                        args: launch_request
 866                            .args
 867                            .into_iter()
 868                            .map(|arg| substitute_variables_in_str(&arg, &task_context))
 869                            .collect::<Option<Vec<_>>>()
 870                            .context("substituting variables in args")?,
 871                        cwd,
 872                        env,
 873                    };
 874
 875                    dap::DebugRequest::Launch(new_launch_request)
 876                }
 877                request @ dap::DebugRequest::Attach(_) => request, // todo(debugger): We should check that process_id is valid and if not show the modal
 878            };
 879            Ok(DebugTaskDefinition {
 880                label,
 881                adapter: DebugAdapterName(adapter),
 882                request,
 883                initialize_args,
 884                stop_on_entry,
 885                tcp_connection,
 886            })
 887        })
 888    }
 889
 890    fn handle_run_in_terminal(
 891        &self,
 892        request: &RunInTerminalRequestArguments,
 893        mut sender: mpsc::Sender<Result<u32>>,
 894        window: &mut Window,
 895        cx: &mut Context<Self>,
 896    ) -> Task<Result<()>> {
 897        let running = cx.entity();
 898        let Ok(project) = self
 899            .workspace
 900            .update(cx, |workspace, _| workspace.project().clone())
 901        else {
 902            return Task::ready(Err(anyhow!("no workspace")));
 903        };
 904        let session = self.session.read(cx);
 905
 906        let cwd = Some(&request.cwd)
 907            .filter(|cwd| cwd.len() > 0)
 908            .map(PathBuf::from)
 909            .or_else(|| session.binary().cwd.clone());
 910
 911        let mut args = request.args.clone();
 912
 913        // Handle special case for NodeJS debug adapter
 914        // If only the Node binary path is provided, we set the command to None
 915        // This prevents the NodeJS REPL from appearing, which is not the desired behavior
 916        // The expected usage is for users to provide their own Node command, e.g., `node test.js`
 917        // This allows the NodeJS debug client to attach correctly
 918        let command = if args.len() > 1 {
 919            Some(args.remove(0))
 920        } else {
 921            None
 922        };
 923
 924        let mut envs: HashMap<String, String> = Default::default();
 925        if let Some(Value::Object(env)) = &request.env {
 926            for (key, value) in env {
 927                let value_str = match (key.as_str(), value) {
 928                    (_, Value::String(value)) => value,
 929                    _ => continue,
 930                };
 931
 932                envs.insert(key.clone(), value_str.clone());
 933            }
 934        }
 935
 936        let shell = project.read(cx).terminal_settings(&cwd, cx).shell.clone();
 937        let kind = if let Some(command) = command {
 938            let title = request.title.clone().unwrap_or(command.clone());
 939            TerminalKind::Task(task::SpawnInTerminal {
 940                id: task::TaskId("debug".to_string()),
 941                full_label: title.clone(),
 942                label: title.clone(),
 943                command: command.clone(),
 944                args,
 945                command_label: title.clone(),
 946                cwd,
 947                env: envs,
 948                use_new_terminal: true,
 949                allow_concurrent_runs: true,
 950                reveal: task::RevealStrategy::NoFocus,
 951                reveal_target: task::RevealTarget::Dock,
 952                hide: task::HideStrategy::Never,
 953                shell,
 954                show_summary: false,
 955                show_command: false,
 956                show_rerun: false,
 957            })
 958        } else {
 959            TerminalKind::Shell(cwd.map(|c| c.to_path_buf()))
 960        };
 961
 962        let workspace = self.workspace.clone();
 963        let weak_project = project.downgrade();
 964
 965        let terminal_task = project.update(cx, |project, cx| {
 966            project.create_terminal(kind, window.window_handle(), cx)
 967        });
 968        let terminal_task = cx.spawn_in(window, async move |_, cx| {
 969            let terminal = terminal_task.await?;
 970
 971            let terminal_view = cx.new_window_entity(|window, cx| {
 972                TerminalView::new(
 973                    terminal.clone(),
 974                    workspace,
 975                    None,
 976                    weak_project,
 977                    false,
 978                    window,
 979                    cx,
 980                )
 981            })?;
 982
 983            running.update_in(cx, |running, window, cx| {
 984                running.ensure_pane_item(DebuggerPaneItem::Terminal, window, cx);
 985                running.debug_terminal.update(cx, |debug_terminal, cx| {
 986                    debug_terminal.terminal = Some(terminal_view);
 987                    cx.notify();
 988                });
 989            })?;
 990
 991            terminal.read_with(cx, |terminal, _| {
 992                terminal
 993                    .pty_info
 994                    .pid()
 995                    .map(|pid| pid.as_u32())
 996                    .context("Terminal was spawned but PID was not available")
 997            })?
 998        });
 999
1000        cx.background_spawn(async move { anyhow::Ok(sender.send(terminal_task.await).await?) })
1001    }
1002
1003    fn create_sub_view(
1004        &self,
1005        item_kind: DebuggerPaneItem,
1006        _pane: &Entity<Pane>,
1007        cx: &mut Context<Self>,
1008    ) -> Box<dyn ItemHandle> {
1009        match item_kind {
1010            DebuggerPaneItem::Console => {
1011                let weak_console = self.console.clone().downgrade();
1012
1013                Box::new(SubView::new(
1014                    self.console.focus_handle(cx),
1015                    self.console.clone().into(),
1016                    item_kind,
1017                    Some(Box::new(move |cx| {
1018                        weak_console
1019                            .read_with(cx, |console, cx| console.show_indicator(cx))
1020                            .unwrap_or_default()
1021                    })),
1022                    cx,
1023                ))
1024            }
1025            DebuggerPaneItem::Variables => Box::new(SubView::new(
1026                self.variable_list.focus_handle(cx),
1027                self.variable_list.clone().into(),
1028                item_kind,
1029                None,
1030                cx,
1031            )),
1032            DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
1033                self.breakpoint_list.focus_handle(cx),
1034                self.breakpoint_list.clone().into(),
1035                item_kind,
1036                None,
1037                cx,
1038            )),
1039            DebuggerPaneItem::Frames => Box::new(SubView::new(
1040                self.stack_frame_list.focus_handle(cx),
1041                self.stack_frame_list.clone().into(),
1042                item_kind,
1043                None,
1044                cx,
1045            )),
1046            DebuggerPaneItem::Modules => Box::new(SubView::new(
1047                self.module_list.focus_handle(cx),
1048                self.module_list.clone().into(),
1049                item_kind,
1050                None,
1051                cx,
1052            )),
1053            DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
1054                self.loaded_sources_list.focus_handle(cx),
1055                self.loaded_sources_list.clone().into(),
1056                item_kind,
1057                None,
1058                cx,
1059            )),
1060            DebuggerPaneItem::Terminal => Box::new(SubView::new(
1061                self.debug_terminal.focus_handle(cx),
1062                self.debug_terminal.clone().into(),
1063                item_kind,
1064                None,
1065                cx,
1066            )),
1067        }
1068    }
1069
1070    pub(crate) fn ensure_pane_item(
1071        &mut self,
1072        item_kind: DebuggerPaneItem,
1073        window: &mut Window,
1074        cx: &mut Context<Self>,
1075    ) {
1076        if self.pane_items_status(cx).get(&item_kind) == Some(&true) {
1077            return;
1078        };
1079        let pane = self.panes.last_pane();
1080        let sub_view = self.create_sub_view(item_kind, &pane, cx);
1081
1082        pane.update(cx, |pane, cx| {
1083            pane.add_item_inner(sub_view, false, false, false, None, window, cx);
1084        })
1085    }
1086
1087    pub(crate) fn add_pane_item(
1088        &mut self,
1089        item_kind: DebuggerPaneItem,
1090        position: Point<Pixels>,
1091        window: &mut Window,
1092        cx: &mut Context<Self>,
1093    ) {
1094        debug_assert!(
1095            item_kind.is_supported(self.session.read(cx).capabilities()),
1096            "We should only allow adding supported item kinds"
1097        );
1098
1099        if let Some(pane) = self.panes.pane_at_pixel_position(position) {
1100            let sub_view = self.create_sub_view(item_kind, pane, cx);
1101
1102            pane.update(cx, |pane, cx| {
1103                pane.add_item(sub_view, false, false, None, window, cx);
1104            })
1105        }
1106    }
1107
1108    pub(crate) fn pane_items_status(&self, cx: &App) -> IndexMap<DebuggerPaneItem, bool> {
1109        let caps = self.session.read(cx).capabilities();
1110        let mut pane_item_status = IndexMap::from_iter(
1111            DebuggerPaneItem::all()
1112                .iter()
1113                .filter(|kind| kind.is_supported(&caps))
1114                .map(|kind| (*kind, false)),
1115        );
1116        self.panes.panes().iter().for_each(|pane| {
1117            pane.read(cx)
1118                .items()
1119                .filter_map(|item| item.act_as::<SubView>(cx))
1120                .for_each(|view| {
1121                    pane_item_status.insert(view.read(cx).kind, true);
1122                });
1123        });
1124
1125        pane_item_status
1126    }
1127
1128    pub(crate) fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
1129        if self._schedule_serialize.is_none() {
1130            self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
1131                cx.background_executor()
1132                    .timer(Duration::from_millis(100))
1133                    .await;
1134
1135                let Some((adapter_name, pane_layout)) = this
1136                    .read_with(cx, |this, cx| {
1137                        let adapter_name = this.session.read(cx).adapter();
1138                        (
1139                            adapter_name,
1140                            persistence::build_serialized_layout(
1141                                &this.panes.root,
1142                                this.dock_axis,
1143                                cx,
1144                            ),
1145                        )
1146                    })
1147                    .ok()
1148                else {
1149                    return;
1150                };
1151
1152                persistence::serialize_pane_layout(adapter_name, pane_layout)
1153                    .await
1154                    .log_err();
1155
1156                this.update(cx, |this, _| {
1157                    this._schedule_serialize.take();
1158                })
1159                .ok();
1160            }));
1161        }
1162    }
1163
1164    pub(crate) fn handle_pane_event(
1165        this: &mut RunningState,
1166        source_pane: &Entity<Pane>,
1167        event: &Event,
1168        window: &mut Window,
1169        cx: &mut Context<RunningState>,
1170    ) {
1171        this.serialize_layout(window, cx);
1172        match event {
1173            Event::Remove { .. } => {
1174                let _did_find_pane = this.panes.remove(&source_pane).is_ok();
1175                debug_assert!(_did_find_pane);
1176                cx.notify();
1177            }
1178            Event::Focus => {
1179                this.active_pane = Some(source_pane.clone());
1180            }
1181            Event::ZoomIn => {
1182                source_pane.update(cx, |pane, cx| {
1183                    pane.set_zoomed(true, cx);
1184                });
1185                cx.notify();
1186            }
1187            Event::ZoomOut => {
1188                source_pane.update(cx, |pane, cx| {
1189                    pane.set_zoomed(false, cx);
1190                });
1191                cx.notify();
1192            }
1193            _ => {}
1194        }
1195    }
1196
1197    pub(crate) fn activate_pane_in_direction(
1198        &mut self,
1199        direction: SplitDirection,
1200        window: &mut Window,
1201        cx: &mut Context<Self>,
1202    ) {
1203        if let Some(pane) = self
1204            .active_pane
1205            .as_ref()
1206            .and_then(|pane| self.panes.find_pane_in_direction(pane, direction, cx))
1207        {
1208            pane.update(cx, |pane, cx| {
1209                pane.focus_active_item(window, cx);
1210            })
1211        } else {
1212            self.workspace
1213                .update(cx, |workspace, cx| {
1214                    workspace.activate_pane_in_direction(direction, window, cx)
1215                })
1216                .ok();
1217        }
1218    }
1219
1220    pub(crate) fn go_to_selected_stack_frame(&self, window: &mut Window, cx: &mut Context<Self>) {
1221        if self.thread_id.is_some() {
1222            self.stack_frame_list
1223                .update(cx, |list, cx| {
1224                    let Some(stack_frame_id) = list.opened_stack_frame_id() else {
1225                        return Task::ready(Ok(()));
1226                    };
1227                    list.go_to_stack_frame(stack_frame_id, window, cx)
1228                })
1229                .detach();
1230        }
1231    }
1232
1233    pub(crate) fn has_open_context_menu(&self, cx: &App) -> bool {
1234        self.variable_list.read(cx).has_open_context_menu()
1235    }
1236
1237    pub fn session(&self) -> &Entity<Session> {
1238        &self.session
1239    }
1240
1241    pub fn session_id(&self) -> SessionId {
1242        self.session_id
1243    }
1244
1245    pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option<dap::StackFrameId> {
1246        self.stack_frame_list.read(cx).opened_stack_frame_id()
1247    }
1248
1249    pub(crate) fn stack_frame_list(&self) -> &Entity<StackFrameList> {
1250        &self.stack_frame_list
1251    }
1252
1253    #[cfg(test)]
1254    pub fn console(&self) -> &Entity<Console> {
1255        &self.console
1256    }
1257
1258    #[cfg(test)]
1259    pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
1260        &self.module_list
1261    }
1262
1263    pub(crate) fn activate_item(&self, item: DebuggerPaneItem, window: &mut Window, cx: &mut App) {
1264        let (variable_list_position, pane) = self
1265            .panes
1266            .panes()
1267            .into_iter()
1268            .find_map(|pane| {
1269                pane.read(cx)
1270                    .items_of_type::<SubView>()
1271                    .position(|view| view.read(cx).view_kind() == item)
1272                    .map(|view| (view, pane))
1273            })
1274            .unwrap();
1275        pane.update(cx, |this, cx| {
1276            this.activate_item(variable_list_position, true, true, window, cx);
1277        })
1278    }
1279
1280    #[cfg(test)]
1281    pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
1282        &self.variable_list
1283    }
1284
1285    #[cfg(test)]
1286    pub(crate) fn serialized_layout(&self, cx: &App) -> SerializedLayout {
1287        persistence::build_serialized_layout(&self.panes.root, self.dock_axis, cx)
1288    }
1289
1290    pub fn capabilities(&self, cx: &App) -> Capabilities {
1291        self.session().read(cx).capabilities().clone()
1292    }
1293
1294    pub fn select_current_thread(
1295        &mut self,
1296        threads: &Vec<(Thread, ThreadStatus)>,
1297        window: &mut Window,
1298        cx: &mut Context<Self>,
1299    ) {
1300        let selected_thread = self
1301            .thread_id
1302            .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0))
1303            .or_else(|| threads.first());
1304
1305        let Some((selected_thread, _)) = selected_thread else {
1306            return;
1307        };
1308
1309        if Some(ThreadId(selected_thread.id)) != self.thread_id {
1310            self.select_thread(ThreadId(selected_thread.id), window, cx);
1311        }
1312    }
1313
1314    pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
1315        self.thread_id
1316    }
1317
1318    pub fn thread_status(&self, cx: &App) -> Option<ThreadStatus> {
1319        self.thread_id
1320            .map(|id| self.session().read(cx).thread_status(id))
1321    }
1322
1323    pub(crate) fn select_thread(
1324        &mut self,
1325        thread_id: ThreadId,
1326        window: &mut Window,
1327        cx: &mut Context<Self>,
1328    ) {
1329        if self.thread_id.is_some_and(|id| id == thread_id) {
1330            return;
1331        }
1332
1333        self.thread_id = Some(thread_id);
1334
1335        self.stack_frame_list
1336            .update(cx, |list, cx| list.schedule_refresh(true, window, cx));
1337    }
1338
1339    pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
1340        let Some(thread_id) = self.thread_id else {
1341            return;
1342        };
1343
1344        self.session().update(cx, |state, cx| {
1345            state.continue_thread(thread_id, cx);
1346        });
1347    }
1348
1349    pub fn step_over(&mut self, cx: &mut Context<Self>) {
1350        let Some(thread_id) = self.thread_id else {
1351            return;
1352        };
1353
1354        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1355
1356        self.session().update(cx, |state, cx| {
1357            state.step_over(thread_id, granularity, cx);
1358        });
1359    }
1360
1361    pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
1362        let Some(thread_id) = self.thread_id else {
1363            return;
1364        };
1365
1366        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1367
1368        self.session().update(cx, |state, cx| {
1369            state.step_in(thread_id, granularity, cx);
1370        });
1371    }
1372
1373    pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
1374        let Some(thread_id) = self.thread_id else {
1375            return;
1376        };
1377
1378        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1379
1380        self.session().update(cx, |state, cx| {
1381            state.step_out(thread_id, granularity, cx);
1382        });
1383    }
1384
1385    pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
1386        let Some(thread_id) = self.thread_id else {
1387            return;
1388        };
1389
1390        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
1391
1392        self.session().update(cx, |state, cx| {
1393            state.step_back(thread_id, granularity, cx);
1394        });
1395    }
1396
1397    pub fn restart_session(&self, cx: &mut Context<Self>) {
1398        self.session().update(cx, |state, cx| {
1399            state.restart(None, cx);
1400        });
1401    }
1402
1403    pub fn pause_thread(&self, cx: &mut Context<Self>) {
1404        let Some(thread_id) = self.thread_id else {
1405            return;
1406        };
1407
1408        self.session().update(cx, |state, cx| {
1409            state.pause_thread(thread_id, cx);
1410        });
1411    }
1412
1413    pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
1414        self.workspace
1415            .update(cx, |workspace, cx| {
1416                workspace
1417                    .project()
1418                    .read(cx)
1419                    .breakpoint_store()
1420                    .update(cx, |store, cx| {
1421                        store.remove_active_position(Some(self.session_id), cx)
1422                    })
1423            })
1424            .log_err();
1425
1426        self.session.update(cx, |session, cx| {
1427            session.shutdown(cx).detach();
1428        })
1429    }
1430
1431    pub fn stop_thread(&self, cx: &mut Context<Self>) {
1432        let Some(thread_id) = self.thread_id else {
1433            return;
1434        };
1435
1436        self.workspace
1437            .update(cx, |workspace, cx| {
1438                workspace
1439                    .project()
1440                    .read(cx)
1441                    .breakpoint_store()
1442                    .update(cx, |store, cx| {
1443                        store.remove_active_position(Some(self.session_id), cx)
1444                    })
1445            })
1446            .log_err();
1447
1448        self.session().update(cx, |state, cx| {
1449            state.terminate_threads(Some(vec![thread_id; 1]), cx);
1450        });
1451    }
1452
1453    pub fn detach_client(&self, cx: &mut Context<Self>) {
1454        self.session().update(cx, |state, cx| {
1455            state.disconnect_client(cx);
1456        });
1457    }
1458
1459    pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context<Self>) {
1460        self.session.update(cx, |session, cx| {
1461            session.toggle_ignore_breakpoints(cx).detach();
1462        });
1463    }
1464
1465    fn default_pane_layout(
1466        project: Entity<Project>,
1467        workspace: &WeakEntity<Workspace>,
1468        stack_frame_list: &Entity<StackFrameList>,
1469        variable_list: &Entity<VariableList>,
1470        console: &Entity<Console>,
1471        breakpoints: &Entity<BreakpointList>,
1472        debug_terminal: &Entity<DebugTerminal>,
1473        dock_axis: Axis,
1474        subscriptions: &mut HashMap<EntityId, Subscription>,
1475        window: &mut Window,
1476        cx: &mut Context<'_, RunningState>,
1477    ) -> Member {
1478        let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1479        leftmost_pane.update(cx, |this, cx| {
1480            this.add_item(
1481                Box::new(SubView::new(
1482                    this.focus_handle(cx),
1483                    stack_frame_list.clone().into(),
1484                    DebuggerPaneItem::Frames,
1485                    None,
1486                    cx,
1487                )),
1488                true,
1489                false,
1490                None,
1491                window,
1492                cx,
1493            );
1494            this.add_item(
1495                Box::new(SubView::new(
1496                    breakpoints.focus_handle(cx),
1497                    breakpoints.clone().into(),
1498                    DebuggerPaneItem::BreakpointList,
1499                    None,
1500                    cx,
1501                )),
1502                true,
1503                false,
1504                None,
1505                window,
1506                cx,
1507            );
1508            this.activate_item(0, false, false, window, cx);
1509        });
1510        let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1511
1512        center_pane.update(cx, |this, cx| {
1513            let weak_console = console.downgrade();
1514            this.add_item(
1515                Box::new(SubView::new(
1516                    console.focus_handle(cx),
1517                    console.clone().into(),
1518                    DebuggerPaneItem::Console,
1519                    Some(Box::new(move |cx| {
1520                        weak_console
1521                            .read_with(cx, |console, cx| console.show_indicator(cx))
1522                            .unwrap_or_default()
1523                    })),
1524                    cx,
1525                )),
1526                true,
1527                false,
1528                None,
1529                window,
1530                cx,
1531            );
1532
1533            this.add_item(
1534                Box::new(SubView::new(
1535                    variable_list.focus_handle(cx),
1536                    variable_list.clone().into(),
1537                    DebuggerPaneItem::Variables,
1538                    None,
1539                    cx,
1540                )),
1541                true,
1542                false,
1543                None,
1544                window,
1545                cx,
1546            );
1547            this.activate_item(0, false, false, window, cx);
1548        });
1549
1550        let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1551        rightmost_pane.update(cx, |this, cx| {
1552            this.add_item(
1553                Box::new(SubView::new(
1554                    debug_terminal.focus_handle(cx),
1555                    debug_terminal.clone().into(),
1556                    DebuggerPaneItem::Terminal,
1557                    None,
1558                    cx,
1559                )),
1560                false,
1561                false,
1562                None,
1563                window,
1564                cx,
1565            );
1566        });
1567
1568        subscriptions.extend(
1569            [&leftmost_pane, &center_pane, &rightmost_pane]
1570                .into_iter()
1571                .map(|entity| {
1572                    (
1573                        entity.entity_id(),
1574                        cx.subscribe_in(entity, window, Self::handle_pane_event),
1575                    )
1576                }),
1577        );
1578
1579        let group_root = workspace::PaneAxis::new(
1580            dock_axis.invert(),
1581            [leftmost_pane, center_pane, rightmost_pane]
1582                .into_iter()
1583                .map(workspace::Member::Pane)
1584                .collect(),
1585        );
1586
1587        Member::Axis(group_root)
1588    }
1589
1590    pub(crate) fn invert_axies(&mut self) {
1591        self.dock_axis = self.dock_axis.invert();
1592        self.panes.invert_axies();
1593    }
1594}
1595
1596impl EventEmitter<DebugPanelItemEvent> for RunningState {}
1597
1598impl Focusable for RunningState {
1599    fn focus_handle(&self, _: &App) -> FocusHandle {
1600        self.focus_handle.clone()
1601    }
1602}