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, sync::Arc, time::Duration};
   9
  10use crate::persistence::{self, DebuggerPaneItem, SerializedPaneLayout};
  11
  12use super::DebugPanelItemEvent;
  13use breakpoint_list::BreakpointList;
  14use collections::{HashMap, IndexMap};
  15use console::Console;
  16use dap::{Capabilities, Thread, client::SessionId, debugger_settings::DebuggerSettings};
  17use gpui::{
  18    Action as _, AnyView, AppContext, Entity, EntityId, EventEmitter, FocusHandle, Focusable,
  19    NoAction, Pixels, Point, Subscription, Task, WeakEntity,
  20};
  21use loaded_source_list::LoadedSourceList;
  22use module_list::ModuleList;
  23use project::{
  24    Project,
  25    debugger::session::{Session, SessionEvent, ThreadId, ThreadStatus},
  26};
  27use rpc::proto::ViewId;
  28use settings::Settings;
  29use stack_frame_list::StackFrameList;
  30use terminal_view::TerminalView;
  31use ui::{
  32    ActiveTheme, AnyElement, App, ButtonCommon as _, Clickable as _, Context, ContextMenu,
  33    DropdownMenu, FluentBuilder, IconButton, IconName, IconSize, InteractiveElement, IntoElement,
  34    Label, LabelCommon as _, ParentElement, Render, SharedString, StatefulInteractiveElement,
  35    Styled, Tab, Tooltip, Window, div, h_flex, v_flex,
  36};
  37use util::ResultExt;
  38use variable_list::VariableList;
  39use workspace::{
  40    ActivePaneDecorator, DraggedTab, Item, ItemHandle, Member, Pane, PaneGroup, Workspace,
  41    item::TabContentParams, move_item, pane::Event,
  42};
  43
  44pub struct RunningState {
  45    session: Entity<Session>,
  46    thread_id: Option<ThreadId>,
  47    focus_handle: FocusHandle,
  48    _remote_id: Option<ViewId>,
  49    workspace: WeakEntity<Workspace>,
  50    session_id: SessionId,
  51    variable_list: Entity<variable_list::VariableList>,
  52    _subscriptions: Vec<Subscription>,
  53    stack_frame_list: Entity<stack_frame_list::StackFrameList>,
  54    loaded_sources_list: Entity<LoadedSourceList>,
  55    pub debug_terminal: Entity<DebugTerminal>,
  56    module_list: Entity<module_list::ModuleList>,
  57    _console: Entity<Console>,
  58    breakpoint_list: Entity<BreakpointList>,
  59    panes: PaneGroup,
  60    pane_close_subscriptions: HashMap<EntityId, Subscription>,
  61    _schedule_serialize: Option<Task<()>>,
  62}
  63
  64impl Render for RunningState {
  65    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
  66        let zoomed_pane = self
  67            .panes
  68            .panes()
  69            .into_iter()
  70            .find(|pane| pane.read(cx).is_zoomed());
  71
  72        let active = self.panes.panes().into_iter().next();
  73        let x = if let Some(ref zoomed_pane) = zoomed_pane {
  74            zoomed_pane.update(cx, |pane, cx| pane.render(window, cx).into_any_element())
  75        } else if let Some(active) = active {
  76            self.panes
  77                .render(
  78                    None,
  79                    &ActivePaneDecorator::new(active, &self.workspace),
  80                    window,
  81                    cx,
  82                )
  83                .into_any_element()
  84        } else {
  85            div().into_any_element()
  86        };
  87        let thread_status = self
  88            .thread_id
  89            .map(|thread_id| self.session.read(cx).thread_status(thread_id))
  90            .unwrap_or(ThreadStatus::Exited);
  91
  92        self.variable_list.update(cx, |this, cx| {
  93            this.disabled(thread_status != ThreadStatus::Stopped, cx);
  94        });
  95        v_flex()
  96            .size_full()
  97            .key_context("DebugSessionItem")
  98            .track_focus(&self.focus_handle(cx))
  99            .child(h_flex().flex_1().child(x))
 100    }
 101}
 102
 103pub(crate) struct SubView {
 104    inner: AnyView,
 105    pane_focus_handle: FocusHandle,
 106    kind: DebuggerPaneItem,
 107    show_indicator: Box<dyn Fn(&App) -> bool>,
 108}
 109
 110impl SubView {
 111    pub(crate) fn new(
 112        pane_focus_handle: FocusHandle,
 113        view: AnyView,
 114        kind: DebuggerPaneItem,
 115        show_indicator: Option<Box<dyn Fn(&App) -> bool>>,
 116        cx: &mut App,
 117    ) -> Entity<Self> {
 118        cx.new(|_| Self {
 119            kind,
 120            inner: view,
 121            pane_focus_handle,
 122            show_indicator: show_indicator.unwrap_or(Box::new(|_| false)),
 123        })
 124    }
 125
 126    pub(crate) fn view_kind(&self) -> DebuggerPaneItem {
 127        self.kind
 128    }
 129}
 130impl Focusable for SubView {
 131    fn focus_handle(&self, _: &App) -> FocusHandle {
 132        self.pane_focus_handle.clone()
 133    }
 134}
 135impl EventEmitter<()> for SubView {}
 136impl Item for SubView {
 137    type Event = ();
 138
 139    /// This is used to serialize debugger pane layouts
 140    /// A SharedString gets converted to a enum and back during serialization/deserialization.
 141    fn tab_content_text(&self, _window: &Window, _cx: &App) -> Option<SharedString> {
 142        Some(self.kind.to_shared_string())
 143    }
 144
 145    fn tab_content(
 146        &self,
 147        params: workspace::item::TabContentParams,
 148        _: &Window,
 149        cx: &App,
 150    ) -> AnyElement {
 151        let label = Label::new(self.kind.to_shared_string())
 152            .size(ui::LabelSize::Small)
 153            .color(params.text_color())
 154            .line_height_style(ui::LineHeightStyle::UiLabel);
 155
 156        if !params.selected && self.show_indicator.as_ref()(cx) {
 157            return h_flex()
 158                .justify_between()
 159                .child(ui::Indicator::dot())
 160                .gap_2()
 161                .child(label)
 162                .into_any_element();
 163        }
 164
 165        label.into_any_element()
 166    }
 167}
 168
 169impl Render for SubView {
 170    fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
 171        v_flex().size_full().child(self.inner.clone())
 172    }
 173}
 174
 175pub(crate) fn new_debugger_pane(
 176    workspace: WeakEntity<Workspace>,
 177    project: Entity<Project>,
 178    window: &mut Window,
 179    cx: &mut Context<RunningState>,
 180) -> Entity<Pane> {
 181    let weak_running = cx.weak_entity();
 182    let custom_drop_handle = {
 183        let workspace = workspace.clone();
 184        let project = project.downgrade();
 185        let weak_running = weak_running.clone();
 186        move |pane: &mut Pane, any: &dyn Any, window: &mut Window, cx: &mut Context<Pane>| {
 187            let Some(tab) = any.downcast_ref::<DraggedTab>() else {
 188                return ControlFlow::Break(());
 189            };
 190            let Some(project) = project.upgrade() else {
 191                return ControlFlow::Break(());
 192            };
 193            let this_pane = cx.entity().clone();
 194            let item = if tab.pane == this_pane {
 195                pane.item_for_index(tab.ix)
 196            } else {
 197                tab.pane.read(cx).item_for_index(tab.ix)
 198            };
 199            let Some(item) = item.filter(|item| item.downcast::<SubView>().is_some()) else {
 200                return ControlFlow::Break(());
 201            };
 202
 203            let source = tab.pane.clone();
 204            let item_id_to_move = item.item_id();
 205
 206            let Ok(new_split_pane) = pane
 207                .drag_split_direction()
 208                .map(|split_direction| {
 209                    weak_running.update(cx, |running, cx| {
 210                        let new_pane =
 211                            new_debugger_pane(workspace.clone(), project.clone(), window, cx);
 212                        let _previous_subscription = running.pane_close_subscriptions.insert(
 213                            new_pane.entity_id(),
 214                            cx.subscribe_in(&new_pane, window, RunningState::handle_pane_event),
 215                        );
 216                        debug_assert!(_previous_subscription.is_none());
 217                        running
 218                            .panes
 219                            .split(&this_pane, &new_pane, split_direction)?;
 220                        anyhow::Ok(new_pane)
 221                    })
 222                })
 223                .transpose()
 224            else {
 225                return ControlFlow::Break(());
 226            };
 227
 228            match new_split_pane.transpose() {
 229                // Source pane may be the one currently updated, so defer the move.
 230                Ok(Some(new_pane)) => cx
 231                    .spawn_in(window, async move |_, cx| {
 232                        cx.update(|window, cx| {
 233                            move_item(
 234                                &source,
 235                                &new_pane,
 236                                item_id_to_move,
 237                                new_pane.read(cx).active_item_index(),
 238                                window,
 239                                cx,
 240                            );
 241                        })
 242                        .ok();
 243                    })
 244                    .detach(),
 245                // If we drop into existing pane or current pane,
 246                // regular pane drop handler will take care of it,
 247                // using the right tab index for the operation.
 248                Ok(None) => return ControlFlow::Continue(()),
 249                err @ Err(_) => {
 250                    err.log_err();
 251                    return ControlFlow::Break(());
 252                }
 253            };
 254
 255            ControlFlow::Break(())
 256        }
 257    };
 258
 259    let ret = cx.new(move |cx| {
 260        let mut pane = Pane::new(
 261            workspace.clone(),
 262            project.clone(),
 263            Default::default(),
 264            None,
 265            NoAction.boxed_clone(),
 266            window,
 267            cx,
 268        );
 269        let focus_handle = pane.focus_handle(cx);
 270        pane.set_can_split(Some(Arc::new({
 271            let weak_running = weak_running.clone();
 272            move |pane, dragged_item, _window, cx| {
 273                if let Some(tab) = dragged_item.downcast_ref::<DraggedTab>() {
 274                    let is_current_pane = tab.pane == cx.entity();
 275                    let Some(can_drag_away) = weak_running
 276                        .update(cx, |running_state, _| {
 277                            let current_panes = running_state.panes.panes();
 278                            !current_panes.contains(&&tab.pane)
 279                                || current_panes.len() > 1
 280                                || (!is_current_pane || pane.items_len() > 1)
 281                        })
 282                        .ok()
 283                    else {
 284                        return false;
 285                    };
 286                    if can_drag_away {
 287                        let item = if is_current_pane {
 288                            pane.item_for_index(tab.ix)
 289                        } else {
 290                            tab.pane.read(cx).item_for_index(tab.ix)
 291                        };
 292                        if let Some(item) = item {
 293                            return item.downcast::<SubView>().is_some();
 294                        }
 295                    }
 296                }
 297                false
 298            }
 299        })));
 300        pane.display_nav_history_buttons(None);
 301        pane.set_custom_drop_handle(cx, custom_drop_handle);
 302        pane.set_should_display_tab_bar(|_, _| true);
 303        pane.set_render_tab_bar_buttons(cx, |_, _, _| (None, None));
 304        pane.set_render_tab_bar(cx, {
 305            move |pane, window, cx| {
 306                let active_pane_item = pane.active_item();
 307                h_flex()
 308                    .justify_between()
 309                    .bg(cx.theme().colors().tab_bar_background)
 310                    .border_b_1()
 311                    .border_color(cx.theme().colors().border)
 312                    .track_focus(&focus_handle)
 313                    .child(
 314                        h_flex()
 315                            .w_full()
 316                            .px_2()
 317                            .gap_1()
 318                            .h(Tab::container_height(cx))
 319                            .drag_over::<DraggedTab>(|bar, _, _, cx| {
 320                                bar.bg(cx.theme().colors().drop_target_background)
 321                            })
 322                            .on_drop(cx.listener(
 323                                move |this, dragged_tab: &DraggedTab, window, cx| {
 324                                    this.drag_split_direction = None;
 325                                    this.handle_tab_drop(dragged_tab, this.items_len(), window, cx)
 326                                },
 327                            ))
 328                            .children(pane.items().enumerate().map(|(ix, item)| {
 329                                let selected = active_pane_item
 330                                    .as_ref()
 331                                    .map_or(false, |active| active.item_id() == item.item_id());
 332                                let item_ = item.boxed_clone();
 333                                div()
 334                                    .id(SharedString::from(format!(
 335                                        "debugger_tab_{}",
 336                                        item.item_id().as_u64()
 337                                    )))
 338                                    .p_1()
 339                                    .rounded_md()
 340                                    .cursor_pointer()
 341                                    .map(|this| {
 342                                        if selected {
 343                                            this.bg(cx.theme().colors().tab_active_background)
 344                                        } else {
 345                                            let hover_color = cx.theme().colors().element_hover;
 346                                            this.hover(|style| style.bg(hover_color))
 347                                        }
 348                                    })
 349                                    .on_click(cx.listener(move |this, _, window, cx| {
 350                                        let index = this.index_for_item(&*item_);
 351                                        if let Some(index) = index {
 352                                            this.activate_item(index, true, true, window, cx);
 353                                        }
 354                                    }))
 355                                    .child(item.tab_content(
 356                                        TabContentParams {
 357                                            selected,
 358                                            ..Default::default()
 359                                        },
 360                                        window,
 361                                        cx,
 362                                    ))
 363                                    .on_drop(cx.listener(
 364                                        move |this, dragged_tab: &DraggedTab, window, cx| {
 365                                            this.drag_split_direction = None;
 366                                            this.handle_tab_drop(dragged_tab, ix, window, cx)
 367                                        },
 368                                    ))
 369                                    .on_drag(
 370                                        DraggedTab {
 371                                            item: item.boxed_clone(),
 372                                            pane: cx.entity().clone(),
 373                                            detail: 0,
 374                                            is_active: selected,
 375                                            ix,
 376                                        },
 377                                        |tab, _, _, cx| cx.new(|_| tab.clone()),
 378                                    )
 379                            })),
 380                    )
 381                    .child({
 382                        let zoomed = pane.is_zoomed();
 383                        IconButton::new(
 384                            "debug-toggle-zoom",
 385                            if zoomed {
 386                                IconName::Minimize
 387                            } else {
 388                                IconName::Maximize
 389                            },
 390                        )
 391                        .icon_size(IconSize::Small)
 392                        .on_click(cx.listener(move |pane, _, window, cx| {
 393                            pane.toggle_zoom(&workspace::ToggleZoom, window, cx);
 394                        }))
 395                        .tooltip({
 396                            let focus_handle = focus_handle.clone();
 397                            move |window, cx| {
 398                                let zoomed_text = if zoomed { "Zoom Out" } else { "Zoom In" };
 399                                Tooltip::for_action_in(
 400                                    zoomed_text,
 401                                    &workspace::ToggleZoom,
 402                                    &focus_handle,
 403                                    window,
 404                                    cx,
 405                                )
 406                            }
 407                        })
 408                    })
 409                    .into_any_element()
 410            }
 411        });
 412        pane
 413    });
 414
 415    ret
 416}
 417
 418pub struct DebugTerminal {
 419    pub terminal: Option<Entity<TerminalView>>,
 420    focus_handle: FocusHandle,
 421}
 422
 423impl DebugTerminal {
 424    fn empty(cx: &mut Context<Self>) -> Self {
 425        Self {
 426            terminal: None,
 427            focus_handle: cx.focus_handle(),
 428        }
 429    }
 430}
 431
 432impl gpui::Render for DebugTerminal {
 433    fn render(&mut self, _window: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
 434        if let Some(terminal) = self.terminal.clone() {
 435            terminal.into_any_element()
 436        } else {
 437            div().track_focus(&self.focus_handle).into_any_element()
 438        }
 439    }
 440}
 441impl Focusable for DebugTerminal {
 442    fn focus_handle(&self, cx: &App) -> FocusHandle {
 443        if let Some(terminal) = self.terminal.as_ref() {
 444            return terminal.focus_handle(cx);
 445        } else {
 446            self.focus_handle.clone()
 447        }
 448    }
 449}
 450
 451impl RunningState {
 452    pub fn new(
 453        session: Entity<Session>,
 454        project: Entity<Project>,
 455        workspace: WeakEntity<Workspace>,
 456        serialized_pane_layout: Option<SerializedPaneLayout>,
 457        window: &mut Window,
 458        cx: &mut Context<Self>,
 459    ) -> Self {
 460        let focus_handle = cx.focus_handle();
 461        let session_id = session.read(cx).session_id();
 462        let weak_state = cx.weak_entity();
 463        let stack_frame_list = cx.new(|cx| {
 464            StackFrameList::new(workspace.clone(), session.clone(), weak_state, window, cx)
 465        });
 466
 467        let debug_terminal = cx.new(DebugTerminal::empty);
 468
 469        let variable_list =
 470            cx.new(|cx| VariableList::new(session.clone(), stack_frame_list.clone(), window, cx));
 471
 472        let module_list = cx.new(|cx| ModuleList::new(session.clone(), workspace.clone(), cx));
 473
 474        let loaded_source_list = cx.new(|cx| LoadedSourceList::new(session.clone(), cx));
 475
 476        let console = cx.new(|cx| {
 477            Console::new(
 478                session.clone(),
 479                stack_frame_list.clone(),
 480                variable_list.clone(),
 481                window,
 482                cx,
 483            )
 484        });
 485
 486        let breakpoint_list = BreakpointList::new(session.clone(), workspace.clone(), &project, cx);
 487
 488        let _subscriptions = vec![
 489            cx.observe(&module_list, |_, _, cx| cx.notify()),
 490            cx.subscribe_in(&session, window, |this, _, event, window, cx| {
 491                match event {
 492                    SessionEvent::Stopped(thread_id) => {
 493                        this.workspace
 494                            .update(cx, |workspace, cx| {
 495                                workspace.open_panel::<crate::DebugPanel>(window, cx);
 496                            })
 497                            .log_err();
 498
 499                        if let Some(thread_id) = thread_id {
 500                            this.select_thread(*thread_id, window, cx);
 501                        }
 502                    }
 503                    SessionEvent::Threads => {
 504                        let threads = this.session.update(cx, |this, cx| this.threads(cx));
 505                        this.select_current_thread(&threads, window, cx);
 506                    }
 507                    SessionEvent::CapabilitiesLoaded => {
 508                        let capabilities = this.capabilities(cx);
 509                        if !capabilities.supports_modules_request.unwrap_or(false) {
 510                            this.remove_pane_item(DebuggerPaneItem::Modules, window, cx);
 511                        }
 512                        if !capabilities
 513                            .supports_loaded_sources_request
 514                            .unwrap_or(false)
 515                        {
 516                            this.remove_pane_item(DebuggerPaneItem::LoadedSources, window, cx);
 517                        }
 518                    }
 519
 520                    _ => {}
 521                }
 522                cx.notify()
 523            }),
 524            cx.on_focus_out(&focus_handle, window, |this, _, window, cx| {
 525                this.serialize_layout(window, cx);
 526            }),
 527        ];
 528
 529        let mut pane_close_subscriptions = HashMap::default();
 530        let panes = if let Some(root) = serialized_pane_layout.and_then(|serialized_layout| {
 531            persistence::deserialize_pane_layout(
 532                serialized_layout,
 533                &workspace,
 534                &project,
 535                &stack_frame_list,
 536                &variable_list,
 537                &module_list,
 538                &console,
 539                &breakpoint_list,
 540                &loaded_source_list,
 541                &debug_terminal,
 542                &mut pane_close_subscriptions,
 543                window,
 544                cx,
 545            )
 546        }) {
 547            workspace::PaneGroup::with_root(root)
 548        } else {
 549            pane_close_subscriptions.clear();
 550
 551            let root = Self::default_pane_layout(
 552                project,
 553                &workspace,
 554                &stack_frame_list,
 555                &variable_list,
 556                &module_list,
 557                &loaded_source_list,
 558                &console,
 559                &breakpoint_list,
 560                &mut pane_close_subscriptions,
 561                window,
 562                cx,
 563            );
 564
 565            workspace::PaneGroup::with_root(root)
 566        };
 567
 568        Self {
 569            session,
 570            workspace,
 571            focus_handle,
 572            variable_list,
 573            _subscriptions,
 574            thread_id: None,
 575            _remote_id: None,
 576            stack_frame_list,
 577            session_id,
 578            panes,
 579            module_list,
 580            _console: console,
 581            breakpoint_list,
 582            loaded_sources_list: loaded_source_list,
 583            pane_close_subscriptions,
 584            debug_terminal,
 585            _schedule_serialize: None,
 586        }
 587    }
 588
 589    pub(crate) fn remove_pane_item(
 590        &mut self,
 591        item_kind: DebuggerPaneItem,
 592        window: &mut Window,
 593        cx: &mut Context<Self>,
 594    ) {
 595        if let Some((pane, item_id)) = self.panes.panes().iter().find_map(|pane| {
 596            Some(pane).zip(
 597                pane.read(cx)
 598                    .items()
 599                    .find(|item| {
 600                        item.act_as::<SubView>(cx)
 601                            .is_some_and(|view| view.read(cx).kind == item_kind)
 602                    })
 603                    .map(|item| item.item_id()),
 604            )
 605        }) {
 606            pane.update(cx, |pane, cx| {
 607                pane.remove_item(item_id, false, true, window, cx)
 608            })
 609        }
 610    }
 611
 612    pub(crate) fn has_pane_at_position(&self, position: Point<Pixels>) -> bool {
 613        self.panes.pane_at_pixel_position(position).is_some()
 614    }
 615
 616    fn create_sub_view(
 617        &self,
 618        item_kind: DebuggerPaneItem,
 619        pane: &Entity<Pane>,
 620        cx: &mut Context<Self>,
 621    ) -> Box<dyn ItemHandle> {
 622        match item_kind {
 623            DebuggerPaneItem::Console => {
 624                let weak_console = self._console.clone().downgrade();
 625
 626                Box::new(SubView::new(
 627                    pane.focus_handle(cx),
 628                    self._console.clone().into(),
 629                    item_kind,
 630                    Some(Box::new(move |cx| {
 631                        weak_console
 632                            .read_with(cx, |console, cx| console.show_indicator(cx))
 633                            .unwrap_or_default()
 634                    })),
 635                    cx,
 636                ))
 637            }
 638            DebuggerPaneItem::Variables => Box::new(SubView::new(
 639                self.variable_list.focus_handle(cx),
 640                self.variable_list.clone().into(),
 641                item_kind,
 642                None,
 643                cx,
 644            )),
 645            DebuggerPaneItem::BreakpointList => Box::new(SubView::new(
 646                self.breakpoint_list.focus_handle(cx),
 647                self.breakpoint_list.clone().into(),
 648                item_kind,
 649                None,
 650                cx,
 651            )),
 652            DebuggerPaneItem::Frames => Box::new(SubView::new(
 653                self.stack_frame_list.focus_handle(cx),
 654                self.stack_frame_list.clone().into(),
 655                item_kind,
 656                None,
 657                cx,
 658            )),
 659            DebuggerPaneItem::Modules => Box::new(SubView::new(
 660                self.module_list.focus_handle(cx),
 661                self.module_list.clone().into(),
 662                item_kind,
 663                None,
 664                cx,
 665            )),
 666            DebuggerPaneItem::LoadedSources => Box::new(SubView::new(
 667                self.loaded_sources_list.focus_handle(cx),
 668                self.loaded_sources_list.clone().into(),
 669                item_kind,
 670                None,
 671                cx,
 672            )),
 673            DebuggerPaneItem::Terminal => Box::new(SubView::new(
 674                self.debug_terminal.focus_handle(cx),
 675                self.debug_terminal.clone().into(),
 676                item_kind,
 677                None,
 678                cx,
 679            )),
 680        }
 681    }
 682
 683    pub(crate) fn ensure_pane_item(
 684        &mut self,
 685        item_kind: DebuggerPaneItem,
 686        window: &mut Window,
 687        cx: &mut Context<Self>,
 688    ) {
 689        if self.pane_items_status(cx).get(&item_kind) == Some(&true) {
 690            return;
 691        };
 692        let pane = self.panes.last_pane();
 693        let sub_view = self.create_sub_view(item_kind, &pane, cx);
 694
 695        pane.update(cx, |pane, cx| {
 696            pane.add_item_inner(sub_view, false, false, false, None, window, cx);
 697        })
 698    }
 699
 700    pub(crate) fn add_pane_item(
 701        &mut self,
 702        item_kind: DebuggerPaneItem,
 703        position: Point<Pixels>,
 704        window: &mut Window,
 705        cx: &mut Context<Self>,
 706    ) {
 707        debug_assert!(
 708            item_kind.is_supported(self.session.read(cx).capabilities()),
 709            "We should only allow adding supported item kinds"
 710        );
 711
 712        if let Some(pane) = self.panes.pane_at_pixel_position(position) {
 713            let sub_view = self.create_sub_view(item_kind, pane, cx);
 714
 715            pane.update(cx, |pane, cx| {
 716                pane.add_item(sub_view, false, false, None, window, cx);
 717            })
 718        }
 719    }
 720
 721    pub(crate) fn pane_items_status(&self, cx: &App) -> IndexMap<DebuggerPaneItem, bool> {
 722        let caps = self.session.read(cx).capabilities();
 723        let mut pane_item_status = IndexMap::from_iter(
 724            DebuggerPaneItem::all()
 725                .iter()
 726                .filter(|kind| kind.is_supported(&caps))
 727                .map(|kind| (*kind, false)),
 728        );
 729        self.panes.panes().iter().for_each(|pane| {
 730            pane.read(cx)
 731                .items()
 732                .filter_map(|item| item.act_as::<SubView>(cx))
 733                .for_each(|view| {
 734                    pane_item_status.insert(view.read(cx).kind, true);
 735                });
 736        });
 737
 738        pane_item_status
 739    }
 740
 741    pub(crate) fn serialize_layout(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 742        if self._schedule_serialize.is_none() {
 743            self._schedule_serialize = Some(cx.spawn_in(window, async move |this, cx| {
 744                cx.background_executor()
 745                    .timer(Duration::from_millis(100))
 746                    .await;
 747
 748                let Some((adapter_name, pane_group)) = this
 749                    .update(cx, |this, cx| {
 750                        let adapter_name = this.session.read(cx).adapter_name();
 751                        (
 752                            adapter_name,
 753                            persistence::build_serialized_pane_layout(&this.panes.root, cx),
 754                        )
 755                    })
 756                    .ok()
 757                else {
 758                    return;
 759                };
 760
 761                persistence::serialize_pane_layout(adapter_name, pane_group)
 762                    .await
 763                    .log_err();
 764
 765                this.update(cx, |this, _| {
 766                    this._schedule_serialize.take();
 767                })
 768                .ok();
 769            }));
 770        }
 771    }
 772
 773    pub(crate) fn handle_pane_event(
 774        this: &mut RunningState,
 775        source_pane: &Entity<Pane>,
 776        event: &Event,
 777        window: &mut Window,
 778        cx: &mut Context<RunningState>,
 779    ) {
 780        this.serialize_layout(window, cx);
 781        match event {
 782            Event::Remove { .. } => {
 783                let _did_find_pane = this.panes.remove(&source_pane).is_ok();
 784                debug_assert!(_did_find_pane);
 785                cx.notify();
 786            }
 787            Event::ZoomIn => {
 788                source_pane.update(cx, |pane, cx| {
 789                    pane.set_zoomed(true, cx);
 790                });
 791                cx.notify();
 792            }
 793            Event::ZoomOut => {
 794                source_pane.update(cx, |pane, cx| {
 795                    pane.set_zoomed(false, cx);
 796                });
 797                cx.notify();
 798            }
 799            _ => {}
 800        }
 801    }
 802
 803    pub(crate) fn go_to_selected_stack_frame(&self, window: &Window, cx: &mut Context<Self>) {
 804        if self.thread_id.is_some() {
 805            self.stack_frame_list
 806                .update(cx, |list, cx| list.go_to_selected_stack_frame(window, cx));
 807        }
 808    }
 809
 810    pub(crate) fn has_open_context_menu(&self, cx: &App) -> bool {
 811        self.variable_list.read(cx).has_open_context_menu()
 812    }
 813
 814    pub fn session(&self) -> &Entity<Session> {
 815        &self.session
 816    }
 817
 818    pub fn session_id(&self) -> SessionId {
 819        self.session_id
 820    }
 821
 822    pub(crate) fn selected_stack_frame_id(&self, cx: &App) -> Option<dap::StackFrameId> {
 823        self.stack_frame_list.read(cx).selected_stack_frame_id()
 824    }
 825
 826    #[cfg(test)]
 827    pub fn stack_frame_list(&self) -> &Entity<StackFrameList> {
 828        &self.stack_frame_list
 829    }
 830
 831    #[cfg(test)]
 832    pub fn console(&self) -> &Entity<Console> {
 833        &self._console
 834    }
 835
 836    #[cfg(test)]
 837    pub(crate) fn module_list(&self) -> &Entity<ModuleList> {
 838        &self.module_list
 839    }
 840
 841    #[cfg(test)]
 842    pub(crate) fn activate_modules_list(&self, window: &mut Window, cx: &mut App) {
 843        let (variable_list_position, pane) = self
 844            .panes
 845            .panes()
 846            .into_iter()
 847            .find_map(|pane| {
 848                pane.read(cx)
 849                    .items_of_type::<SubView>()
 850                    .position(|view| view.read(cx).view_kind().to_shared_string() == *"Modules")
 851                    .map(|view| (view, pane))
 852            })
 853            .unwrap();
 854        pane.update(cx, |this, cx| {
 855            this.activate_item(variable_list_position, true, true, window, cx);
 856        })
 857    }
 858    #[cfg(test)]
 859    pub(crate) fn variable_list(&self) -> &Entity<VariableList> {
 860        &self.variable_list
 861    }
 862
 863    pub fn capabilities(&self, cx: &App) -> Capabilities {
 864        self.session().read(cx).capabilities().clone()
 865    }
 866
 867    pub fn select_current_thread(
 868        &mut self,
 869        threads: &Vec<(Thread, ThreadStatus)>,
 870        window: &mut Window,
 871        cx: &mut Context<Self>,
 872    ) {
 873        let selected_thread = self
 874            .thread_id
 875            .and_then(|thread_id| threads.iter().find(|(thread, _)| thread.id == thread_id.0))
 876            .or_else(|| threads.first());
 877
 878        let Some((selected_thread, _)) = selected_thread else {
 879            return;
 880        };
 881
 882        if Some(ThreadId(selected_thread.id)) != self.thread_id {
 883            self.select_thread(ThreadId(selected_thread.id), window, cx);
 884        }
 885    }
 886
 887    pub(crate) fn selected_thread_id(&self) -> Option<ThreadId> {
 888        self.thread_id
 889    }
 890
 891    pub fn thread_status(&self, cx: &App) -> Option<ThreadStatus> {
 892        self.thread_id
 893            .map(|id| self.session().read(cx).thread_status(id))
 894    }
 895
 896    fn select_thread(&mut self, thread_id: ThreadId, window: &mut Window, cx: &mut Context<Self>) {
 897        if self.thread_id.is_some_and(|id| id == thread_id) {
 898            return;
 899        }
 900
 901        self.thread_id = Some(thread_id);
 902
 903        self.stack_frame_list
 904            .update(cx, |list, cx| list.schedule_refresh(true, window, cx));
 905    }
 906
 907    pub fn continue_thread(&mut self, cx: &mut Context<Self>) {
 908        let Some(thread_id) = self.thread_id else {
 909            return;
 910        };
 911
 912        self.session().update(cx, |state, cx| {
 913            state.continue_thread(thread_id, cx);
 914        });
 915    }
 916
 917    pub fn step_over(&mut self, cx: &mut Context<Self>) {
 918        let Some(thread_id) = self.thread_id else {
 919            return;
 920        };
 921
 922        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
 923
 924        self.session().update(cx, |state, cx| {
 925            state.step_over(thread_id, granularity, cx);
 926        });
 927    }
 928
 929    pub(crate) fn step_in(&mut self, cx: &mut Context<Self>) {
 930        let Some(thread_id) = self.thread_id else {
 931            return;
 932        };
 933
 934        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
 935
 936        self.session().update(cx, |state, cx| {
 937            state.step_in(thread_id, granularity, cx);
 938        });
 939    }
 940
 941    pub(crate) fn step_out(&mut self, cx: &mut Context<Self>) {
 942        let Some(thread_id) = self.thread_id else {
 943            return;
 944        };
 945
 946        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
 947
 948        self.session().update(cx, |state, cx| {
 949            state.step_out(thread_id, granularity, cx);
 950        });
 951    }
 952
 953    pub(crate) fn step_back(&mut self, cx: &mut Context<Self>) {
 954        let Some(thread_id) = self.thread_id else {
 955            return;
 956        };
 957
 958        let granularity = DebuggerSettings::get_global(cx).stepping_granularity;
 959
 960        self.session().update(cx, |state, cx| {
 961            state.step_back(thread_id, granularity, cx);
 962        });
 963    }
 964
 965    pub fn restart_session(&self, cx: &mut Context<Self>) {
 966        self.session().update(cx, |state, cx| {
 967            state.restart(None, cx);
 968        });
 969    }
 970
 971    pub fn pause_thread(&self, cx: &mut Context<Self>) {
 972        let Some(thread_id) = self.thread_id else {
 973            return;
 974        };
 975
 976        self.session().update(cx, |state, cx| {
 977            state.pause_thread(thread_id, cx);
 978        });
 979    }
 980
 981    pub(crate) fn shutdown(&mut self, cx: &mut Context<Self>) {
 982        self.workspace
 983            .update(cx, |workspace, cx| {
 984                workspace
 985                    .project()
 986                    .read(cx)
 987                    .breakpoint_store()
 988                    .update(cx, |store, cx| {
 989                        store.remove_active_position(Some(self.session_id), cx)
 990                    })
 991            })
 992            .log_err();
 993
 994        self.session.update(cx, |session, cx| {
 995            session.shutdown(cx).detach();
 996        })
 997    }
 998
 999    pub fn stop_thread(&self, cx: &mut Context<Self>) {
1000        let Some(thread_id) = self.thread_id else {
1001            return;
1002        };
1003
1004        self.workspace
1005            .update(cx, |workspace, cx| {
1006                workspace
1007                    .project()
1008                    .read(cx)
1009                    .breakpoint_store()
1010                    .update(cx, |store, cx| {
1011                        store.remove_active_position(Some(self.session_id), cx)
1012                    })
1013            })
1014            .log_err();
1015
1016        self.session().update(cx, |state, cx| {
1017            state.terminate_threads(Some(vec![thread_id; 1]), cx);
1018        });
1019    }
1020
1021    #[expect(
1022        unused,
1023        reason = "Support for disconnecting a client is not wired through yet"
1024    )]
1025    pub fn disconnect_client(&self, cx: &mut Context<Self>) {
1026        self.session().update(cx, |state, cx| {
1027            state.disconnect_client(cx);
1028        });
1029    }
1030
1031    pub fn toggle_ignore_breakpoints(&mut self, cx: &mut Context<Self>) {
1032        self.session.update(cx, |session, cx| {
1033            session.toggle_ignore_breakpoints(cx).detach();
1034        });
1035    }
1036
1037    pub(crate) fn thread_dropdown(
1038        &self,
1039        window: &mut Window,
1040        cx: &mut Context<'_, RunningState>,
1041    ) -> DropdownMenu {
1042        let state = cx.entity();
1043        let threads = self.session.update(cx, |this, cx| this.threads(cx));
1044        let selected_thread_name = threads
1045            .iter()
1046            .find(|(thread, _)| self.thread_id.map(|id| id.0) == Some(thread.id))
1047            .map(|(thread, _)| thread.name.clone())
1048            .unwrap_or("Threads".to_owned());
1049        DropdownMenu::new(
1050            ("thread-list", self.session_id.0),
1051            selected_thread_name,
1052            ContextMenu::build_eager(window, cx, move |mut this, _, _| {
1053                for (thread, _) in threads {
1054                    let state = state.clone();
1055                    let thread_id = thread.id;
1056                    this = this.entry(thread.name, None, move |window, cx| {
1057                        state.update(cx, |state, cx| {
1058                            state.select_thread(ThreadId(thread_id), window, cx);
1059                        });
1060                    });
1061                }
1062                this
1063            }),
1064        )
1065    }
1066
1067    fn default_pane_layout(
1068        project: Entity<Project>,
1069        workspace: &WeakEntity<Workspace>,
1070        stack_frame_list: &Entity<StackFrameList>,
1071        variable_list: &Entity<VariableList>,
1072        module_list: &Entity<ModuleList>,
1073        loaded_source_list: &Entity<LoadedSourceList>,
1074        console: &Entity<Console>,
1075        breakpoints: &Entity<BreakpointList>,
1076        subscriptions: &mut HashMap<EntityId, Subscription>,
1077        window: &mut Window,
1078        cx: &mut Context<'_, RunningState>,
1079    ) -> Member {
1080        let leftmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1081        leftmost_pane.update(cx, |this, cx| {
1082            this.add_item(
1083                Box::new(SubView::new(
1084                    this.focus_handle(cx),
1085                    stack_frame_list.clone().into(),
1086                    DebuggerPaneItem::Frames,
1087                    None,
1088                    cx,
1089                )),
1090                true,
1091                false,
1092                None,
1093                window,
1094                cx,
1095            );
1096            this.add_item(
1097                Box::new(SubView::new(
1098                    breakpoints.focus_handle(cx),
1099                    breakpoints.clone().into(),
1100                    DebuggerPaneItem::BreakpointList,
1101                    None,
1102                    cx,
1103                )),
1104                true,
1105                false,
1106                None,
1107                window,
1108                cx,
1109            );
1110            this.activate_item(0, false, false, window, cx);
1111        });
1112        let center_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1113
1114        center_pane.update(cx, |this, cx| {
1115            this.add_item(
1116                Box::new(SubView::new(
1117                    variable_list.focus_handle(cx),
1118                    variable_list.clone().into(),
1119                    DebuggerPaneItem::Variables,
1120                    None,
1121                    cx,
1122                )),
1123                true,
1124                false,
1125                None,
1126                window,
1127                cx,
1128            );
1129            this.add_item(
1130                Box::new(SubView::new(
1131                    module_list.focus_handle(cx),
1132                    module_list.clone().into(),
1133                    DebuggerPaneItem::Modules,
1134                    None,
1135                    cx,
1136                )),
1137                false,
1138                false,
1139                None,
1140                window,
1141                cx,
1142            );
1143
1144            this.add_item(
1145                Box::new(SubView::new(
1146                    loaded_source_list.focus_handle(cx),
1147                    loaded_source_list.clone().into(),
1148                    DebuggerPaneItem::LoadedSources,
1149                    None,
1150                    cx,
1151                )),
1152                false,
1153                false,
1154                None,
1155                window,
1156                cx,
1157            );
1158            this.activate_item(0, false, false, window, cx);
1159        });
1160
1161        let rightmost_pane = new_debugger_pane(workspace.clone(), project.clone(), window, cx);
1162        rightmost_pane.update(cx, |this, cx| {
1163            let weak_console = console.downgrade();
1164            this.add_item(
1165                Box::new(SubView::new(
1166                    this.focus_handle(cx),
1167                    console.clone().into(),
1168                    DebuggerPaneItem::Console,
1169                    Some(Box::new(move |cx| {
1170                        weak_console
1171                            .read_with(cx, |console, cx| console.show_indicator(cx))
1172                            .unwrap_or_default()
1173                    })),
1174                    cx,
1175                )),
1176                true,
1177                false,
1178                None,
1179                window,
1180                cx,
1181            );
1182        });
1183
1184        subscriptions.extend(
1185            [&leftmost_pane, &center_pane, &rightmost_pane]
1186                .into_iter()
1187                .map(|entity| {
1188                    (
1189                        entity.entity_id(),
1190                        cx.subscribe_in(entity, window, Self::handle_pane_event),
1191                    )
1192                }),
1193        );
1194
1195        let group_root = workspace::PaneAxis::new(
1196            gpui::Axis::Horizontal,
1197            [leftmost_pane, center_pane, rightmost_pane]
1198                .into_iter()
1199                .map(workspace::Member::Pane)
1200                .collect(),
1201        );
1202
1203        Member::Axis(group_root)
1204    }
1205}
1206
1207impl EventEmitter<DebugPanelItemEvent> for RunningState {}
1208
1209impl Focusable for RunningState {
1210    fn focus_handle(&self, _: &App) -> FocusHandle {
1211        self.focus_handle.clone()
1212    }
1213}