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