debugger_panel.rs

   1use crate::persistence::DebuggerPaneItem;
   2use crate::session::DebugSession;
   3use crate::session::running::RunningState;
   4use crate::{
   5    ClearAllBreakpoints, Continue, Detach, FocusBreakpointList, FocusConsole, FocusFrames,
   6    FocusLoadedSources, FocusModules, FocusTerminal, FocusVariables, NewProcessModal,
   7    NewProcessMode, Pause, Restart, StepInto, StepOut, StepOver, Stop, ToggleExpandItem,
   8    ToggleSessionPicker, ToggleThreadPicker, persistence, spawn_task_or_modal,
   9};
  10use anyhow::Result;
  11use dap::adapters::DebugAdapterName;
  12use dap::debugger_settings::DebugPanelDockPosition;
  13use dap::{
  14    ContinuedEvent, LoadedSourceEvent, ModuleEvent, OutputEvent, StoppedEvent, ThreadEvent,
  15    client::SessionId, debugger_settings::DebuggerSettings,
  16};
  17use dap::{DapRegistry, StartDebuggingRequestArguments};
  18use gpui::{
  19    Action, App, AsyncWindowContext, Context, DismissEvent, Entity, EntityId, EventEmitter,
  20    FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task, WeakEntity,
  21    actions, anchored, deferred,
  22};
  23
  24use language::Buffer;
  25use project::debugger::session::{Session, SessionStateEvent};
  26use project::{Fs, WorktreeId};
  27use project::{Project, debugger::session::ThreadStatus};
  28use rpc::proto::{self};
  29use settings::Settings;
  30use std::sync::Arc;
  31use task::{DebugScenario, TaskContext};
  32use ui::{ContextMenu, Divider, PopoverMenuHandle, Tooltip, prelude::*};
  33use workspace::SplitDirection;
  34use workspace::{
  35    Pane, Workspace,
  36    dock::{DockPosition, Panel, PanelEvent},
  37};
  38
  39pub enum DebugPanelEvent {
  40    Exited(SessionId),
  41    Terminated(SessionId),
  42    Stopped {
  43        client_id: SessionId,
  44        event: StoppedEvent,
  45        go_to_stack_frame: bool,
  46    },
  47    Thread((SessionId, ThreadEvent)),
  48    Continued((SessionId, ContinuedEvent)),
  49    Output((SessionId, OutputEvent)),
  50    Module((SessionId, ModuleEvent)),
  51    LoadedSource((SessionId, LoadedSourceEvent)),
  52    ClientShutdown(SessionId),
  53    CapabilitiesChanged(SessionId),
  54}
  55
  56actions!(debug_panel, [ToggleFocus]);
  57
  58pub struct DebugPanel {
  59    size: Pixels,
  60    sessions: Vec<Entity<DebugSession>>,
  61    active_session: Option<Entity<DebugSession>>,
  62    project: Entity<Project>,
  63    workspace: WeakEntity<Workspace>,
  64    focus_handle: FocusHandle,
  65    context_menu: Option<(Entity<ContextMenu>, Point<Pixels>, Subscription)>,
  66    debug_scenario_scheduled_last: bool,
  67    pub(crate) thread_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
  68    pub(crate) session_picker_menu_handle: PopoverMenuHandle<ContextMenu>,
  69    fs: Arc<dyn Fs>,
  70    is_zoomed: bool,
  71    _subscriptions: [Subscription; 1],
  72}
  73
  74impl DebugPanel {
  75    pub fn new(
  76        workspace: &Workspace,
  77        window: &mut Window,
  78        cx: &mut Context<Workspace>,
  79    ) -> Entity<Self> {
  80        cx.new(|cx| {
  81            let project = workspace.project().clone();
  82            let focus_handle = cx.focus_handle();
  83            let thread_picker_menu_handle = PopoverMenuHandle::default();
  84            let session_picker_menu_handle = PopoverMenuHandle::default();
  85
  86            let focus_subscription = cx.on_focus(
  87                &focus_handle,
  88                window,
  89                |this: &mut DebugPanel, window, cx| {
  90                    this.focus_active_item(window, cx);
  91                },
  92            );
  93
  94            Self {
  95                size: px(300.),
  96                sessions: vec![],
  97                active_session: None,
  98                focus_handle,
  99                project,
 100                workspace: workspace.weak_handle(),
 101                context_menu: None,
 102                fs: workspace.app_state().fs.clone(),
 103                thread_picker_menu_handle,
 104                session_picker_menu_handle,
 105                is_zoomed: false,
 106                _subscriptions: [focus_subscription],
 107                debug_scenario_scheduled_last: true,
 108            }
 109        })
 110    }
 111
 112    pub(crate) fn focus_active_item(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 113        let Some(session) = self.active_session.clone() else {
 114            return;
 115        };
 116        let active_pane = session
 117            .read(cx)
 118            .running_state()
 119            .read(cx)
 120            .active_pane()
 121            .clone();
 122        active_pane.update(cx, |pane, cx| {
 123            pane.focus_active_item(window, cx);
 124        });
 125    }
 126
 127    pub(crate) fn sessions(&self) -> Vec<Entity<DebugSession>> {
 128        self.sessions.clone()
 129    }
 130
 131    pub fn active_session(&self) -> Option<Entity<DebugSession>> {
 132        self.active_session.clone()
 133    }
 134
 135    pub(crate) fn running_state(&self, cx: &mut App) -> Option<Entity<RunningState>> {
 136        self.active_session()
 137            .map(|session| session.read(cx).running_state().clone())
 138    }
 139
 140    pub fn load(
 141        workspace: WeakEntity<Workspace>,
 142        cx: &mut AsyncWindowContext,
 143    ) -> Task<Result<Entity<Self>>> {
 144        cx.spawn(async move |cx| {
 145            workspace.update_in(cx, |workspace, window, cx| {
 146                let debug_panel = DebugPanel::new(workspace, window, cx);
 147
 148                workspace.register_action(|workspace, _: &ClearAllBreakpoints, _, cx| {
 149                    workspace.project().read(cx).breakpoint_store().update(
 150                        cx,
 151                        |breakpoint_store, cx| {
 152                            breakpoint_store.clear_breakpoints(cx);
 153                        },
 154                    )
 155                });
 156
 157                workspace.set_debugger_provider(DebuggerProvider(debug_panel.clone()));
 158
 159                debug_panel
 160            })
 161        })
 162    }
 163
 164    pub fn start_session(
 165        &mut self,
 166        scenario: DebugScenario,
 167        task_context: TaskContext,
 168        active_buffer: Option<Entity<Buffer>>,
 169        worktree_id: Option<WorktreeId>,
 170        window: &mut Window,
 171        cx: &mut Context<Self>,
 172    ) {
 173        let dap_store = self.project.read(cx).dap_store();
 174        let session = dap_store.update(cx, |dap_store, cx| {
 175            dap_store.new_session(
 176                scenario.label.clone(),
 177                DebugAdapterName(scenario.adapter.clone()),
 178                None,
 179                cx,
 180            )
 181        });
 182        let worktree = worktree_id.or_else(|| {
 183            active_buffer
 184                .as_ref()
 185                .and_then(|buffer| buffer.read(cx).file())
 186                .map(|f| f.worktree_id(cx))
 187        });
 188        let Some(worktree) = worktree
 189            .and_then(|id| self.project.read(cx).worktree_for_id(id, cx))
 190            .or_else(|| self.project.read(cx).visible_worktrees(cx).next())
 191        else {
 192            log::debug!("Could not find a worktree to spawn the debug session in");
 193            return;
 194        };
 195        self.debug_scenario_scheduled_last = true;
 196        if let Some(inventory) = self
 197            .project
 198            .read(cx)
 199            .task_store()
 200            .read(cx)
 201            .task_inventory()
 202            .cloned()
 203        {
 204            inventory.update(cx, |inventory, _| {
 205                inventory.scenario_scheduled(scenario.clone());
 206            })
 207        }
 208        let task = cx.spawn_in(window, {
 209            let session = session.clone();
 210            async move |this, cx| {
 211                let debug_session =
 212                    Self::register_session(this.clone(), session.clone(), true, cx).await?;
 213                let definition = debug_session
 214                    .update_in(cx, |debug_session, window, cx| {
 215                        debug_session.running_state().update(cx, |running, cx| {
 216                            running.resolve_scenario(
 217                                scenario,
 218                                task_context,
 219                                active_buffer,
 220                                worktree_id,
 221                                window,
 222                                cx,
 223                            )
 224                        })
 225                    })?
 226                    .await?;
 227                dap_store
 228                    .update(cx, |dap_store, cx| {
 229                        dap_store.boot_session(session.clone(), definition, worktree, cx)
 230                    })?
 231                    .await
 232            }
 233        });
 234
 235        cx.spawn(async move |_, cx| {
 236            if let Err(error) = task.await {
 237                log::error!("{error}");
 238                session
 239                    .update(cx, |session, cx| {
 240                        session
 241                            .console_output(cx)
 242                            .unbounded_send(format!("error: {}", error))
 243                            .ok();
 244                        session.shutdown(cx)
 245                    })?
 246                    .await;
 247            }
 248            anyhow::Ok(())
 249        })
 250        .detach_and_log_err(cx);
 251    }
 252
 253    pub(crate) fn rerun_last_session(
 254        &mut self,
 255        workspace: &mut Workspace,
 256        window: &mut Window,
 257        cx: &mut Context<Self>,
 258    ) {
 259        let task_store = workspace.project().read(cx).task_store().clone();
 260        let Some(task_inventory) = task_store.read(cx).task_inventory() else {
 261            return;
 262        };
 263        let workspace = self.workspace.clone();
 264        let Some(scenario) = task_inventory.read(cx).last_scheduled_scenario().cloned() else {
 265            window.defer(cx, move |window, cx| {
 266                workspace
 267                    .update(cx, |workspace, cx| {
 268                        NewProcessModal::show(workspace, window, NewProcessMode::Debug, None, cx);
 269                    })
 270                    .ok();
 271            });
 272            return;
 273        };
 274
 275        cx.spawn_in(window, async move |this, cx| {
 276            let task_contexts = workspace
 277                .update_in(cx, |workspace, window, cx| {
 278                    tasks_ui::task_contexts(workspace, window, cx)
 279                })?
 280                .await;
 281
 282            let task_context = task_contexts.active_context().cloned().unwrap_or_default();
 283            let worktree_id = task_contexts.worktree();
 284
 285            this.update_in(cx, |this, window, cx| {
 286                this.start_session(
 287                    scenario.clone(),
 288                    task_context,
 289                    None,
 290                    worktree_id,
 291                    window,
 292                    cx,
 293                );
 294            })
 295        })
 296        .detach();
 297    }
 298
 299    pub(crate) async fn register_session(
 300        this: WeakEntity<Self>,
 301        session: Entity<Session>,
 302        focus: bool,
 303        cx: &mut AsyncWindowContext,
 304    ) -> Result<Entity<DebugSession>> {
 305        let debug_session = register_session_inner(&this, session, cx).await?;
 306
 307        let workspace = this.update_in(cx, |this, window, cx| {
 308            if focus {
 309                this.activate_session(debug_session.clone(), window, cx);
 310            }
 311
 312            this.workspace.clone()
 313        })?;
 314        workspace.update_in(cx, |workspace, window, cx| {
 315            workspace.focus_panel::<Self>(window, cx);
 316        })?;
 317        Ok(debug_session)
 318    }
 319
 320    pub(crate) fn handle_restart_request(
 321        &mut self,
 322        mut curr_session: Entity<Session>,
 323        window: &mut Window,
 324        cx: &mut Context<Self>,
 325    ) {
 326        while let Some(parent_session) = curr_session.read(cx).parent_session().cloned() {
 327            curr_session = parent_session;
 328        }
 329
 330        let Some(worktree) = curr_session.read(cx).worktree() else {
 331            log::error!("Attempted to restart a non-running session");
 332            return;
 333        };
 334
 335        let dap_store_handle = self.project.read(cx).dap_store().clone();
 336        let label = curr_session.read(cx).label().clone();
 337        let adapter = curr_session.read(cx).adapter().clone();
 338        let binary = curr_session.read(cx).binary().clone();
 339        let task = curr_session.update(cx, |session, cx| session.shutdown(cx));
 340
 341        cx.spawn_in(window, async move |this, cx| {
 342            task.await;
 343
 344            let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
 345                let session = dap_store.new_session(label, adapter, None, cx);
 346
 347                let task = session.update(cx, |session, cx| {
 348                    session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
 349                });
 350                (session, task)
 351            })?;
 352            Self::register_session(this.clone(), session.clone(), true, cx).await?;
 353
 354            if let Err(error) = task.await {
 355                session
 356                    .update(cx, |session, cx| {
 357                        session
 358                            .console_output(cx)
 359                            .unbounded_send(format!(
 360                                "Session failed to restart with error: {}",
 361                                error
 362                            ))
 363                            .ok();
 364                        session.shutdown(cx)
 365                    })?
 366                    .await;
 367
 368                return Err(error);
 369            };
 370
 371            Ok(())
 372        })
 373        .detach_and_log_err(cx);
 374    }
 375
 376    pub fn handle_start_debugging_request(
 377        &mut self,
 378        request: &StartDebuggingRequestArguments,
 379        parent_session: Entity<Session>,
 380        window: &mut Window,
 381        cx: &mut Context<Self>,
 382    ) {
 383        let Some(worktree) = parent_session.read(cx).worktree() else {
 384            log::error!("Attempted to start a child-session from a non-running session");
 385            return;
 386        };
 387
 388        let dap_store_handle = self.project.read(cx).dap_store().clone();
 389        let label = self.label_for_child_session(&parent_session, request, cx);
 390        let adapter = parent_session.read(cx).adapter().clone();
 391        let mut binary = parent_session.read(cx).binary().clone();
 392        binary.request_args = request.clone();
 393        cx.spawn_in(window, async move |this, cx| {
 394            let (session, task) = dap_store_handle.update(cx, |dap_store, cx| {
 395                let session =
 396                    dap_store.new_session(label, adapter, Some(parent_session.clone()), cx);
 397
 398                let task = session.update(cx, |session, cx| {
 399                    session.boot(binary, worktree, dap_store_handle.downgrade(), cx)
 400                });
 401                (session, task)
 402            })?;
 403            Self::register_session(this, session, false, cx).await?;
 404            task.await
 405        })
 406        .detach_and_log_err(cx);
 407    }
 408
 409    pub(crate) fn close_session(
 410        &mut self,
 411        entity_id: EntityId,
 412        window: &mut Window,
 413        cx: &mut Context<Self>,
 414    ) {
 415        let Some(session) = self
 416            .sessions
 417            .iter()
 418            .find(|other| entity_id == other.entity_id())
 419            .cloned()
 420        else {
 421            return;
 422        };
 423        session.update(cx, |this, cx| {
 424            this.running_state().update(cx, |this, cx| {
 425                this.serialize_layout(window, cx);
 426            });
 427        });
 428        let session_id = session.update(cx, |this, cx| this.session_id(cx));
 429        let should_prompt = self
 430            .project
 431            .update(cx, |this, cx| {
 432                let session = this.dap_store().read(cx).session_by_id(session_id);
 433                session.map(|session| !session.read(cx).is_terminated())
 434            })
 435            .unwrap_or_default();
 436
 437        cx.spawn_in(window, async move |this, cx| {
 438            if should_prompt {
 439                let response = cx.prompt(
 440                    gpui::PromptLevel::Warning,
 441                    "This Debug Session is still running. Are you sure you want to terminate it?",
 442                    None,
 443                    &["Yes", "No"],
 444                );
 445                if response.await == Ok(1) {
 446                    return;
 447                }
 448            }
 449            session.update(cx, |session, cx| session.shutdown(cx)).ok();
 450            this.update(cx, |this, cx| {
 451                this.sessions.retain(|other| entity_id != other.entity_id());
 452
 453                if let Some(active_session_id) = this
 454                    .active_session
 455                    .as_ref()
 456                    .map(|session| session.entity_id())
 457                {
 458                    if active_session_id == entity_id {
 459                        this.active_session = this.sessions.first().cloned();
 460                    }
 461                }
 462                cx.notify()
 463            })
 464            .ok();
 465        })
 466        .detach();
 467    }
 468
 469    pub(crate) fn deploy_context_menu(
 470        &mut self,
 471        position: Point<Pixels>,
 472        window: &mut Window,
 473        cx: &mut Context<Self>,
 474    ) {
 475        if let Some(running_state) = self
 476            .active_session
 477            .as_ref()
 478            .map(|session| session.read(cx).running_state().clone())
 479        {
 480            let pane_items_status = running_state.read(cx).pane_items_status(cx);
 481            let this = cx.weak_entity();
 482
 483            let context_menu = ContextMenu::build(window, cx, |mut menu, _window, _cx| {
 484                for (item_kind, is_visible) in pane_items_status.into_iter() {
 485                    menu = menu.toggleable_entry(item_kind, is_visible, IconPosition::End, None, {
 486                        let this = this.clone();
 487                        move |window, cx| {
 488                            this.update(cx, |this, cx| {
 489                                if let Some(running_state) = this
 490                                    .active_session
 491                                    .as_ref()
 492                                    .map(|session| session.read(cx).running_state().clone())
 493                                {
 494                                    running_state.update(cx, |state, cx| {
 495                                        if is_visible {
 496                                            state.remove_pane_item(item_kind, window, cx);
 497                                        } else {
 498                                            state.add_pane_item(item_kind, position, window, cx);
 499                                        }
 500                                    })
 501                                }
 502                            })
 503                            .ok();
 504                        }
 505                    });
 506                }
 507
 508                menu
 509            });
 510
 511            window.focus(&context_menu.focus_handle(cx));
 512            let subscription = cx.subscribe(&context_menu, |this, _, _: &DismissEvent, cx| {
 513                this.context_menu.take();
 514                cx.notify();
 515            });
 516            self.context_menu = Some((context_menu, position, subscription));
 517        }
 518    }
 519
 520    pub(crate) fn top_controls_strip(
 521        &mut self,
 522        window: &mut Window,
 523        cx: &mut Context<Self>,
 524    ) -> Option<Div> {
 525        let active_session = self.active_session.clone();
 526        let focus_handle = self.focus_handle.clone();
 527        let is_side = self.position(window, cx).axis() == gpui::Axis::Horizontal;
 528        let div = if is_side { v_flex() } else { h_flex() };
 529
 530        let new_session_button = || {
 531            IconButton::new("debug-new-session", IconName::Plus)
 532                .icon_size(IconSize::Small)
 533                .on_click({
 534                    move |_, window, cx| window.dispatch_action(crate::Start.boxed_clone(), cx)
 535                })
 536                .tooltip({
 537                    let focus_handle = focus_handle.clone();
 538                    move |window, cx| {
 539                        Tooltip::for_action_in(
 540                            "Start Debug Session",
 541                            &crate::Start,
 542                            &focus_handle,
 543                            window,
 544                            cx,
 545                        )
 546                    }
 547                })
 548        };
 549        let documentation_button = || {
 550            IconButton::new("debug-open-documentation", IconName::CircleHelp)
 551                .icon_size(IconSize::Small)
 552                .on_click(move |_, _, cx| cx.open_url("https://zed.dev/docs/debugger"))
 553                .tooltip(Tooltip::text("Open Documentation"))
 554        };
 555
 556        Some(
 557            div.border_b_1()
 558                .border_color(cx.theme().colors().border)
 559                .p_1()
 560                .justify_between()
 561                .w_full()
 562                .when(is_side, |this| this.gap_1())
 563                .child(
 564                    h_flex()
 565                        .child(
 566                            h_flex().gap_2().w_full().when_some(
 567                                active_session
 568                                    .as_ref()
 569                                    .map(|session| session.read(cx).running_state()),
 570                                |this, running_state| {
 571                                    let thread_status =
 572                                        running_state.read(cx).thread_status(cx).unwrap_or(
 573                                            project::debugger::session::ThreadStatus::Exited,
 574                                        );
 575                                    let capabilities = running_state.read(cx).capabilities(cx);
 576                                    let supports_detach =
 577                                        running_state.read(cx).session().read(cx).is_attached();
 578                                    this.map(|this| {
 579                                        if thread_status == ThreadStatus::Running {
 580                                            this.child(
 581                                                IconButton::new(
 582                                                    "debug-pause",
 583                                                    IconName::DebugPause,
 584                                                )
 585                                                .icon_size(IconSize::XSmall)
 586                                                .shape(ui::IconButtonShape::Square)
 587                                                .on_click(window.listener_for(
 588                                                    &running_state,
 589                                                    |this, _, _window, cx| {
 590                                                        this.pause_thread(cx);
 591                                                    },
 592                                                ))
 593                                                .tooltip({
 594                                                    let focus_handle = focus_handle.clone();
 595                                                    move |window, cx| {
 596                                                        Tooltip::for_action_in(
 597                                                            "Pause program",
 598                                                            &Pause,
 599                                                            &focus_handle,
 600                                                            window,
 601                                                            cx,
 602                                                        )
 603                                                    }
 604                                                }),
 605                                            )
 606                                        } else {
 607                                            this.child(
 608                                                IconButton::new(
 609                                                    "debug-continue",
 610                                                    IconName::DebugContinue,
 611                                                )
 612                                                .icon_size(IconSize::XSmall)
 613                                                .shape(ui::IconButtonShape::Square)
 614                                                .on_click(window.listener_for(
 615                                                    &running_state,
 616                                                    |this, _, _window, cx| this.continue_thread(cx),
 617                                                ))
 618                                                .disabled(thread_status != ThreadStatus::Stopped)
 619                                                .tooltip({
 620                                                    let focus_handle = focus_handle.clone();
 621                                                    move |window, cx| {
 622                                                        Tooltip::for_action_in(
 623                                                            "Continue program",
 624                                                            &Continue,
 625                                                            &focus_handle,
 626                                                            window,
 627                                                            cx,
 628                                                        )
 629                                                    }
 630                                                }),
 631                                            )
 632                                        }
 633                                    })
 634                                    .child(
 635                                        IconButton::new("debug-step-over", IconName::ArrowRight)
 636                                            .icon_size(IconSize::XSmall)
 637                                            .shape(ui::IconButtonShape::Square)
 638                                            .on_click(window.listener_for(
 639                                                &running_state,
 640                                                |this, _, _window, cx| {
 641                                                    this.step_over(cx);
 642                                                },
 643                                            ))
 644                                            .disabled(thread_status != ThreadStatus::Stopped)
 645                                            .tooltip({
 646                                                let focus_handle = focus_handle.clone();
 647                                                move |window, cx| {
 648                                                    Tooltip::for_action_in(
 649                                                        "Step over",
 650                                                        &StepOver,
 651                                                        &focus_handle,
 652                                                        window,
 653                                                        cx,
 654                                                    )
 655                                                }
 656                                            }),
 657                                    )
 658                                    .child(
 659                                        IconButton::new("debug-step-out", IconName::ArrowUpRight)
 660                                            .icon_size(IconSize::XSmall)
 661                                            .shape(ui::IconButtonShape::Square)
 662                                            .on_click(window.listener_for(
 663                                                &running_state,
 664                                                |this, _, _window, cx| {
 665                                                    this.step_out(cx);
 666                                                },
 667                                            ))
 668                                            .disabled(thread_status != ThreadStatus::Stopped)
 669                                            .tooltip({
 670                                                let focus_handle = focus_handle.clone();
 671                                                move |window, cx| {
 672                                                    Tooltip::for_action_in(
 673                                                        "Step out",
 674                                                        &StepOut,
 675                                                        &focus_handle,
 676                                                        window,
 677                                                        cx,
 678                                                    )
 679                                                }
 680                                            }),
 681                                    )
 682                                    .child(
 683                                        IconButton::new(
 684                                            "debug-step-into",
 685                                            IconName::ArrowDownRight,
 686                                        )
 687                                        .icon_size(IconSize::XSmall)
 688                                        .shape(ui::IconButtonShape::Square)
 689                                        .on_click(window.listener_for(
 690                                            &running_state,
 691                                            |this, _, _window, cx| {
 692                                                this.step_in(cx);
 693                                            },
 694                                        ))
 695                                        .disabled(thread_status != ThreadStatus::Stopped)
 696                                        .tooltip({
 697                                            let focus_handle = focus_handle.clone();
 698                                            move |window, cx| {
 699                                                Tooltip::for_action_in(
 700                                                    "Step in",
 701                                                    &StepInto,
 702                                                    &focus_handle,
 703                                                    window,
 704                                                    cx,
 705                                                )
 706                                            }
 707                                        }),
 708                                    )
 709                                    .child(Divider::vertical())
 710                                    .child(
 711                                        IconButton::new("debug-restart", IconName::DebugRestart)
 712                                            .icon_size(IconSize::XSmall)
 713                                            .on_click(window.listener_for(
 714                                                &running_state,
 715                                                |this, _, _window, cx| {
 716                                                    this.restart_session(cx);
 717                                                },
 718                                            ))
 719                                            .tooltip({
 720                                                let focus_handle = focus_handle.clone();
 721                                                move |window, cx| {
 722                                                    Tooltip::for_action_in(
 723                                                        "Restart",
 724                                                        &Restart,
 725                                                        &focus_handle,
 726                                                        window,
 727                                                        cx,
 728                                                    )
 729                                                }
 730                                            }),
 731                                    )
 732                                    .child(
 733                                        IconButton::new("debug-stop", IconName::Power)
 734                                            .icon_size(IconSize::XSmall)
 735                                            .on_click(window.listener_for(
 736                                                &running_state,
 737                                                |this, _, _window, cx| {
 738                                                    this.stop_thread(cx);
 739                                                },
 740                                            ))
 741                                            .disabled(
 742                                                thread_status != ThreadStatus::Stopped
 743                                                    && thread_status != ThreadStatus::Running,
 744                                            )
 745                                            .tooltip({
 746                                                let focus_handle = focus_handle.clone();
 747                                                let label = if capabilities
 748                                                    .supports_terminate_threads_request
 749                                                    .unwrap_or_default()
 750                                                {
 751                                                    "Terminate Thread"
 752                                                } else {
 753                                                    "Terminate All Threads"
 754                                                };
 755                                                move |window, cx| {
 756                                                    Tooltip::for_action_in(
 757                                                        label,
 758                                                        &Stop,
 759                                                        &focus_handle,
 760                                                        window,
 761                                                        cx,
 762                                                    )
 763                                                }
 764                                            }),
 765                                    )
 766                                    .when(
 767                                        supports_detach,
 768                                        |div| {
 769                                            div.child(
 770                                                IconButton::new(
 771                                                    "debug-disconnect",
 772                                                    IconName::DebugDetach,
 773                                                )
 774                                                .disabled(
 775                                                    thread_status != ThreadStatus::Stopped
 776                                                        && thread_status != ThreadStatus::Running,
 777                                                )
 778                                                .icon_size(IconSize::XSmall)
 779                                                .on_click(window.listener_for(
 780                                                    &running_state,
 781                                                    |this, _, _, cx| {
 782                                                        this.detach_client(cx);
 783                                                    },
 784                                                ))
 785                                                .tooltip({
 786                                                    let focus_handle = focus_handle.clone();
 787                                                    move |window, cx| {
 788                                                        Tooltip::for_action_in(
 789                                                            "Detach",
 790                                                            &Detach,
 791                                                            &focus_handle,
 792                                                            window,
 793                                                            cx,
 794                                                        )
 795                                                    }
 796                                                }),
 797                                            )
 798                                        },
 799                                    )
 800                                },
 801                            ),
 802                        )
 803                        .justify_around()
 804                        .when(is_side, |this| {
 805                            this.child(new_session_button())
 806                                .child(documentation_button())
 807                        }),
 808                )
 809                .child(
 810                    h_flex()
 811                        .gap_2()
 812                        .when(is_side, |this| this.justify_between())
 813                        .child(
 814                            h_flex().when_some(
 815                                active_session
 816                                    .as_ref()
 817                                    .map(|session| session.read(cx).running_state())
 818                                    .cloned(),
 819                                |this, running_state| {
 820                                    this.children({
 821                                        let running_state = running_state.clone();
 822                                        let threads =
 823                                            running_state.update(cx, |running_state, cx| {
 824                                                let session = running_state.session();
 825                                                session
 826                                                    .update(cx, |session, cx| session.threads(cx))
 827                                            });
 828
 829                                        self.render_thread_dropdown(
 830                                            &running_state,
 831                                            threads,
 832                                            window,
 833                                            cx,
 834                                        )
 835                                    })
 836                                    .when(!is_side, |this| this.gap_2().child(Divider::vertical()))
 837                                },
 838                            ),
 839                        )
 840                        .child(
 841                            h_flex()
 842                                .children(self.render_session_menu(
 843                                    self.active_session(),
 844                                    self.running_state(cx),
 845                                    window,
 846                                    cx,
 847                                ))
 848                                .when(!is_side, |this| {
 849                                    this.child(new_session_button())
 850                                        .child(documentation_button())
 851                                }),
 852                        ),
 853                ),
 854        )
 855    }
 856
 857    pub(crate) fn activate_pane_in_direction(
 858        &mut self,
 859        direction: SplitDirection,
 860        window: &mut Window,
 861        cx: &mut Context<Self>,
 862    ) {
 863        if let Some(session) = self.active_session() {
 864            session.update(cx, |session, cx| {
 865                session.running_state().update(cx, |running, cx| {
 866                    running.activate_pane_in_direction(direction, window, cx);
 867                })
 868            });
 869        }
 870    }
 871
 872    pub(crate) fn activate_item(
 873        &mut self,
 874        item: DebuggerPaneItem,
 875        window: &mut Window,
 876        cx: &mut Context<Self>,
 877    ) {
 878        if let Some(session) = self.active_session() {
 879            session.update(cx, |session, cx| {
 880                session.running_state().update(cx, |running, cx| {
 881                    running.activate_item(item, window, cx);
 882                });
 883            });
 884        }
 885    }
 886
 887    pub(crate) fn activate_session_by_id(
 888        &mut self,
 889        session_id: SessionId,
 890        window: &mut Window,
 891        cx: &mut Context<Self>,
 892    ) {
 893        if let Some(session) = self
 894            .sessions
 895            .iter()
 896            .find(|session| session.read(cx).session_id(cx) == session_id)
 897        {
 898            self.activate_session(session.clone(), window, cx);
 899        }
 900    }
 901
 902    pub(crate) fn activate_session(
 903        &mut self,
 904        session_item: Entity<DebugSession>,
 905        window: &mut Window,
 906        cx: &mut Context<Self>,
 907    ) {
 908        debug_assert!(self.sessions.contains(&session_item));
 909        session_item.focus_handle(cx).focus(window);
 910        session_item.update(cx, |this, cx| {
 911            this.running_state().update(cx, |this, cx| {
 912                this.go_to_selected_stack_frame(window, cx);
 913            });
 914        });
 915        self.active_session = Some(session_item);
 916        cx.notify();
 917    }
 918
 919    // TODO: restore once we have proper comment preserving file edits
 920    // pub(crate) fn save_scenario(
 921    //     &self,
 922    //     scenario: &DebugScenario,
 923    //     worktree_id: WorktreeId,
 924    //     window: &mut Window,
 925    //     cx: &mut App,
 926    // ) -> Task<Result<ProjectPath>> {
 927    //     self.workspace
 928    //         .update(cx, |workspace, cx| {
 929    //             let Some(mut path) = workspace.absolute_path_of_worktree(worktree_id, cx) else {
 930    //                 return Task::ready(Err(anyhow!("Couldn't get worktree path")));
 931    //             };
 932
 933    //             let serialized_scenario = serde_json::to_value(scenario);
 934
 935    //             cx.spawn_in(window, async move |workspace, cx| {
 936    //                 let serialized_scenario = serialized_scenario?;
 937    //                 let fs =
 938    //                     workspace.read_with(cx, |workspace, _| workspace.app_state().fs.clone())?;
 939
 940    //                 path.push(paths::local_settings_folder_relative_path());
 941    //                 if !fs.is_dir(path.as_path()).await {
 942    //                     fs.create_dir(path.as_path()).await?;
 943    //                 }
 944    //                 path.pop();
 945
 946    //                 path.push(paths::local_debug_file_relative_path());
 947    //                 let path = path.as_path();
 948
 949    //                 if !fs.is_file(path).await {
 950    //                     fs.create_file(path, Default::default()).await?;
 951    //                     fs.write(
 952    //                         path,
 953    //                         initial_local_debug_tasks_content().to_string().as_bytes(),
 954    //                     )
 955    //                     .await?;
 956    //                 }
 957
 958    //                 let content = fs.load(path).await?;
 959    //                 let mut values =
 960    //                     serde_json_lenient::from_str::<Vec<serde_json::Value>>(&content)?;
 961    //                 values.push(serialized_scenario);
 962    //                 fs.save(
 963    //                     path,
 964    //                     &serde_json_lenient::to_string_pretty(&values).map(Into::into)?,
 965    //                     Default::default(),
 966    //                 )
 967    //                 .await?;
 968
 969    //                 workspace.update(cx, |workspace, cx| {
 970    //                     workspace
 971    //                         .project()
 972    //                         .read(cx)
 973    //                         .project_path_for_absolute_path(&path, cx)
 974    //                         .context(
 975    //                             "Couldn't get project path for .zed/debug.json in active worktree",
 976    //                         )
 977    //                 })?
 978    //             })
 979    //         })
 980    //         .unwrap_or_else(|err| Task::ready(Err(err)))
 981    // }
 982
 983    pub(crate) fn toggle_thread_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 984        self.thread_picker_menu_handle.toggle(window, cx);
 985    }
 986
 987    pub(crate) fn toggle_session_picker(&mut self, window: &mut Window, cx: &mut Context<Self>) {
 988        self.session_picker_menu_handle.toggle(window, cx);
 989    }
 990
 991    fn toggle_zoom(
 992        &mut self,
 993        _: &workspace::ToggleZoom,
 994        window: &mut Window,
 995        cx: &mut Context<Self>,
 996    ) {
 997        if self.is_zoomed {
 998            cx.emit(PanelEvent::ZoomOut);
 999        } else {
1000            if !self.focus_handle(cx).contains_focused(window, cx) {
1001                cx.focus_self(window);
1002            }
1003            cx.emit(PanelEvent::ZoomIn);
1004        }
1005    }
1006
1007    fn label_for_child_session(
1008        &self,
1009        parent_session: &Entity<Session>,
1010        request: &StartDebuggingRequestArguments,
1011        cx: &mut Context<'_, Self>,
1012    ) -> SharedString {
1013        let adapter = parent_session.read(cx).adapter();
1014        if let Some(adapter) = DapRegistry::global(cx).adapter(&adapter) {
1015            if let Some(label) = adapter.label_for_child_session(request) {
1016                return label.into();
1017            }
1018        }
1019        let mut label = parent_session.read(cx).label().clone();
1020        if !label.ends_with("(child)") {
1021            label = format!("{label} (child)").into();
1022        }
1023        label
1024    }
1025}
1026
1027async fn register_session_inner(
1028    this: &WeakEntity<DebugPanel>,
1029    session: Entity<Session>,
1030    cx: &mut AsyncWindowContext,
1031) -> Result<Entity<DebugSession>> {
1032    let adapter_name = session.read_with(cx, |session, _| session.adapter())?;
1033    this.update_in(cx, |_, window, cx| {
1034        cx.subscribe_in(
1035            &session,
1036            window,
1037            move |this, session, event: &SessionStateEvent, window, cx| match event {
1038                SessionStateEvent::Restart => {
1039                    this.handle_restart_request(session.clone(), window, cx);
1040                }
1041                SessionStateEvent::SpawnChildSession { request } => {
1042                    this.handle_start_debugging_request(request, session.clone(), window, cx);
1043                }
1044                _ => {}
1045            },
1046        )
1047        .detach();
1048    })
1049    .ok();
1050    let serialized_layout = persistence::get_serialized_layout(adapter_name).await;
1051    let debug_session = this.update_in(cx, |this, window, cx| {
1052        let parent_session = this
1053            .sessions
1054            .iter()
1055            .find(|p| Some(p.read(cx).session_id(cx)) == session.read(cx).parent_id(cx))
1056            .cloned();
1057        this.sessions.retain(|session| {
1058            !session
1059                .read(cx)
1060                .running_state()
1061                .read(cx)
1062                .session()
1063                .read(cx)
1064                .is_terminated()
1065        });
1066
1067        let debug_session = DebugSession::running(
1068            this.project.clone(),
1069            this.workspace.clone(),
1070            parent_session.map(|p| p.read(cx).running_state().read(cx).debug_terminal.clone()),
1071            session,
1072            serialized_layout,
1073            this.position(window, cx).axis(),
1074            window,
1075            cx,
1076        );
1077
1078        // We might want to make this an event subscription and only notify when a new thread is selected
1079        // This is used to filter the command menu correctly
1080        cx.observe(
1081            &debug_session.read(cx).running_state().clone(),
1082            |_, _, cx| cx.notify(),
1083        )
1084        .detach();
1085
1086        this.sessions.push(debug_session.clone());
1087
1088        debug_session
1089    })?;
1090    Ok(debug_session)
1091}
1092
1093impl EventEmitter<PanelEvent> for DebugPanel {}
1094impl EventEmitter<DebugPanelEvent> for DebugPanel {}
1095
1096impl Focusable for DebugPanel {
1097    fn focus_handle(&self, _: &App) -> FocusHandle {
1098        self.focus_handle.clone()
1099    }
1100}
1101
1102impl Panel for DebugPanel {
1103    fn persistent_name() -> &'static str {
1104        "DebugPanel"
1105    }
1106
1107    fn position(&self, _window: &Window, cx: &App) -> DockPosition {
1108        match DebuggerSettings::get_global(cx).dock {
1109            DebugPanelDockPosition::Left => DockPosition::Left,
1110            DebugPanelDockPosition::Bottom => DockPosition::Bottom,
1111            DebugPanelDockPosition::Right => DockPosition::Right,
1112        }
1113    }
1114
1115    fn position_is_valid(&self, _: DockPosition) -> bool {
1116        true
1117    }
1118
1119    fn set_position(
1120        &mut self,
1121        position: DockPosition,
1122        window: &mut Window,
1123        cx: &mut Context<Self>,
1124    ) {
1125        if position.axis() != self.position(window, cx).axis() {
1126            self.sessions.iter().for_each(|session_item| {
1127                session_item.update(cx, |item, cx| {
1128                    item.running_state()
1129                        .update(cx, |state, _| state.invert_axies())
1130                })
1131            });
1132        }
1133
1134        settings::update_settings_file::<DebuggerSettings>(
1135            self.fs.clone(),
1136            cx,
1137            move |settings, _| {
1138                let dock = match position {
1139                    DockPosition::Left => DebugPanelDockPosition::Left,
1140                    DockPosition::Bottom => DebugPanelDockPosition::Bottom,
1141                    DockPosition::Right => DebugPanelDockPosition::Right,
1142                };
1143                settings.dock = dock;
1144            },
1145        );
1146    }
1147
1148    fn size(&self, _window: &Window, _: &App) -> Pixels {
1149        self.size
1150    }
1151
1152    fn set_size(&mut self, size: Option<Pixels>, _window: &mut Window, _cx: &mut Context<Self>) {
1153        self.size = size.unwrap_or(px(300.));
1154    }
1155
1156    fn remote_id() -> Option<proto::PanelId> {
1157        Some(proto::PanelId::DebugPanel)
1158    }
1159
1160    fn icon(&self, _window: &Window, _cx: &App) -> Option<IconName> {
1161        Some(IconName::Debug)
1162    }
1163
1164    fn icon_tooltip(&self, _window: &Window, cx: &App) -> Option<&'static str> {
1165        if DebuggerSettings::get_global(cx).button {
1166            Some("Debug Panel")
1167        } else {
1168            None
1169        }
1170    }
1171
1172    fn toggle_action(&self) -> Box<dyn Action> {
1173        Box::new(ToggleFocus)
1174    }
1175
1176    fn pane(&self) -> Option<Entity<Pane>> {
1177        None
1178    }
1179
1180    fn activation_priority(&self) -> u32 {
1181        9
1182    }
1183
1184    fn set_active(&mut self, _: bool, _: &mut Window, _: &mut Context<Self>) {}
1185
1186    fn is_zoomed(&self, _window: &Window, _cx: &App) -> bool {
1187        self.is_zoomed
1188    }
1189
1190    fn set_zoomed(&mut self, zoomed: bool, _window: &mut Window, cx: &mut Context<Self>) {
1191        self.is_zoomed = zoomed;
1192        cx.notify();
1193    }
1194}
1195
1196impl Render for DebugPanel {
1197    fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1198        let has_sessions = self.sessions.len() > 0;
1199        let this = cx.weak_entity();
1200        debug_assert_eq!(has_sessions, self.active_session.is_some());
1201
1202        if self
1203            .active_session
1204            .as_ref()
1205            .map(|session| session.read(cx).running_state())
1206            .map(|state| state.read(cx).has_open_context_menu(cx))
1207            .unwrap_or(false)
1208        {
1209            self.context_menu.take();
1210        }
1211
1212        v_flex()
1213            .size_full()
1214            .key_context("DebugPanel")
1215            .child(h_flex().children(self.top_controls_strip(window, cx)))
1216            .track_focus(&self.focus_handle(cx))
1217            .on_action({
1218                let this = this.clone();
1219                move |_: &workspace::ActivatePaneLeft, window, cx| {
1220                    this.update(cx, |this, cx| {
1221                        this.activate_pane_in_direction(SplitDirection::Left, window, cx);
1222                    })
1223                    .ok();
1224                }
1225            })
1226            .on_action({
1227                let this = this.clone();
1228                move |_: &workspace::ActivatePaneRight, window, cx| {
1229                    this.update(cx, |this, cx| {
1230                        this.activate_pane_in_direction(SplitDirection::Right, window, cx);
1231                    })
1232                    .ok();
1233                }
1234            })
1235            .on_action({
1236                let this = this.clone();
1237                move |_: &workspace::ActivatePaneUp, window, cx| {
1238                    this.update(cx, |this, cx| {
1239                        this.activate_pane_in_direction(SplitDirection::Up, window, cx);
1240                    })
1241                    .ok();
1242                }
1243            })
1244            .on_action({
1245                let this = this.clone();
1246                move |_: &workspace::ActivatePaneDown, window, cx| {
1247                    this.update(cx, |this, cx| {
1248                        this.activate_pane_in_direction(SplitDirection::Down, window, cx);
1249                    })
1250                    .ok();
1251                }
1252            })
1253            .on_action({
1254                let this = this.clone();
1255                move |_: &FocusConsole, window, cx| {
1256                    this.update(cx, |this, cx| {
1257                        this.activate_item(DebuggerPaneItem::Console, window, cx);
1258                    })
1259                    .ok();
1260                }
1261            })
1262            .on_action({
1263                let this = this.clone();
1264                move |_: &FocusVariables, window, cx| {
1265                    this.update(cx, |this, cx| {
1266                        this.activate_item(DebuggerPaneItem::Variables, window, cx);
1267                    })
1268                    .ok();
1269                }
1270            })
1271            .on_action({
1272                let this = this.clone();
1273                move |_: &FocusBreakpointList, window, cx| {
1274                    this.update(cx, |this, cx| {
1275                        this.activate_item(DebuggerPaneItem::BreakpointList, window, cx);
1276                    })
1277                    .ok();
1278                }
1279            })
1280            .on_action({
1281                let this = this.clone();
1282                move |_: &FocusFrames, window, cx| {
1283                    this.update(cx, |this, cx| {
1284                        this.activate_item(DebuggerPaneItem::Frames, window, cx);
1285                    })
1286                    .ok();
1287                }
1288            })
1289            .on_action({
1290                let this = this.clone();
1291                move |_: &FocusModules, window, cx| {
1292                    this.update(cx, |this, cx| {
1293                        this.activate_item(DebuggerPaneItem::Modules, window, cx);
1294                    })
1295                    .ok();
1296                }
1297            })
1298            .on_action({
1299                let this = this.clone();
1300                move |_: &FocusLoadedSources, window, cx| {
1301                    this.update(cx, |this, cx| {
1302                        this.activate_item(DebuggerPaneItem::LoadedSources, window, cx);
1303                    })
1304                    .ok();
1305                }
1306            })
1307            .on_action({
1308                let this = this.clone();
1309                move |_: &FocusTerminal, window, cx| {
1310                    this.update(cx, |this, cx| {
1311                        this.activate_item(DebuggerPaneItem::Terminal, window, cx);
1312                    })
1313                    .ok();
1314                }
1315            })
1316            .on_action({
1317                let this = this.clone();
1318                move |_: &ToggleThreadPicker, window, cx| {
1319                    this.update(cx, |this, cx| {
1320                        this.toggle_thread_picker(window, cx);
1321                    })
1322                    .ok();
1323                }
1324            })
1325            .on_action({
1326                let this = this.clone();
1327                move |_: &ToggleSessionPicker, window, cx| {
1328                    this.update(cx, |this, cx| {
1329                        this.toggle_session_picker(window, cx);
1330                    })
1331                    .ok();
1332                }
1333            })
1334            .on_action(cx.listener(Self::toggle_zoom))
1335            .on_action(cx.listener(|panel, _: &ToggleExpandItem, _, cx| {
1336                let Some(session) = panel.active_session() else {
1337                    return;
1338                };
1339                let active_pane = session
1340                    .read(cx)
1341                    .running_state()
1342                    .read(cx)
1343                    .active_pane()
1344                    .clone();
1345                active_pane.update(cx, |pane, cx| {
1346                    let is_zoomed = pane.is_zoomed();
1347                    pane.set_zoomed(!is_zoomed, cx);
1348                });
1349                cx.notify();
1350            }))
1351            .when(self.active_session.is_some(), |this| {
1352                this.on_mouse_down(
1353                    MouseButton::Right,
1354                    cx.listener(|this, event: &MouseDownEvent, window, cx| {
1355                        if this
1356                            .active_session
1357                            .as_ref()
1358                            .map(|session| {
1359                                let state = session.read(cx).running_state();
1360                                state.read(cx).has_pane_at_position(event.position)
1361                            })
1362                            .unwrap_or(false)
1363                        {
1364                            this.deploy_context_menu(event.position, window, cx);
1365                        }
1366                    }),
1367                )
1368                .children(self.context_menu.as_ref().map(|(menu, position, _)| {
1369                    deferred(
1370                        anchored()
1371                            .position(*position)
1372                            .anchor(gpui::Corner::TopLeft)
1373                            .child(menu.clone()),
1374                    )
1375                    .with_priority(1)
1376                }))
1377            })
1378            .map(|this| {
1379                if has_sessions {
1380                    this.children(self.active_session.clone())
1381                } else {
1382                    this.child(
1383                        v_flex()
1384                            .h_full()
1385                            .gap_1()
1386                            .items_center()
1387                            .justify_center()
1388                            .child(
1389                                h_flex().child(
1390                                    Label::new("No Debugging Sessions")
1391                                        .size(LabelSize::Small)
1392                                        .color(Color::Muted),
1393                                ),
1394                            )
1395                            .child(
1396                                h_flex().flex_shrink().child(
1397                                    Button::new("spawn-new-session-empty-state", "New Session")
1398                                        .size(ButtonSize::Large)
1399                                        .on_click(|_, window, cx| {
1400                                            window.dispatch_action(crate::Start.boxed_clone(), cx);
1401                                        }),
1402                                ),
1403                            ),
1404                    )
1405                }
1406            })
1407            .into_any()
1408    }
1409}
1410
1411struct DebuggerProvider(Entity<DebugPanel>);
1412
1413impl workspace::DebuggerProvider for DebuggerProvider {
1414    fn start_session(
1415        &self,
1416        definition: DebugScenario,
1417        context: TaskContext,
1418        buffer: Option<Entity<Buffer>>,
1419        window: &mut Window,
1420        cx: &mut App,
1421    ) {
1422        self.0.update(cx, |_, cx| {
1423            cx.defer_in(window, |this, window, cx| {
1424                this.start_session(definition, context, buffer, None, window, cx);
1425            })
1426        })
1427    }
1428
1429    fn spawn_task_or_modal(
1430        &self,
1431        workspace: &mut Workspace,
1432        action: &tasks_ui::Spawn,
1433        window: &mut Window,
1434        cx: &mut Context<Workspace>,
1435    ) {
1436        spawn_task_or_modal(workspace, action, window, cx);
1437    }
1438
1439    fn debug_scenario_scheduled(&self, cx: &mut App) {
1440        self.0.update(cx, |this, _| {
1441            this.debug_scenario_scheduled_last = true;
1442        });
1443    }
1444
1445    fn task_scheduled(&self, cx: &mut App) {
1446        self.0.update(cx, |this, _| {
1447            this.debug_scenario_scheduled_last = false;
1448        })
1449    }
1450
1451    fn debug_scenario_scheduled_last(&self, cx: &App) -> bool {
1452        self.0.read(cx).debug_scenario_scheduled_last
1453    }
1454
1455    fn active_thread_state(&self, cx: &App) -> Option<ThreadStatus> {
1456        let session = self.0.read(cx).active_session()?;
1457        let thread = session.read(cx).running_state().read(cx).thread_id()?;
1458        session.read(cx).session(cx).read(cx).thread_state(thread)
1459    }
1460}