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