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