session.rs

   1use crate::debugger::breakpoint_store::BreakpointSessionState;
   2
   3use super::breakpoint_store::{
   4    BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason, SourceBreakpoint,
   5};
   6use super::dap_command::{
   7    self, Attach, ConfigurationDone, ContinueCommand, DapCommand, DisconnectCommand,
   8    EvaluateCommand, Initialize, Launch, LoadedSourcesCommand, LocalDapCommand, LocationsCommand,
   9    ModulesCommand, NextCommand, PauseCommand, RestartCommand, RestartStackFrameCommand,
  10    ScopesCommand, SetExceptionBreakpoints, SetVariableValueCommand, StackTraceCommand,
  11    StepBackCommand, StepCommand, StepInCommand, StepOutCommand, TerminateCommand,
  12    TerminateThreadsCommand, ThreadsCommand, VariablesCommand,
  13};
  14use super::dap_store::DapStore;
  15use anyhow::{Context as _, Result, anyhow};
  16use collections::{HashMap, HashSet, IndexMap};
  17use dap::adapters::{DebugAdapterBinary, DebugAdapterName};
  18use dap::messages::Response;
  19use dap::requests::{Request, RunInTerminal, StartDebugging};
  20use dap::{
  21    Capabilities, ContinueArguments, EvaluateArgumentsContext, Module, Source, StackFrameId,
  22    SteppingGranularity, StoppedEvent, VariableReference,
  23    client::{DebugAdapterClient, SessionId},
  24    messages::{Events, Message},
  25};
  26use dap::{
  27    ExceptionBreakpointsFilter, ExceptionFilterOptions, OutputEvent, OutputEventCategory,
  28    RunInTerminalRequestArguments, StackFramePresentationHint, StartDebuggingRequestArguments,
  29};
  30use futures::SinkExt;
  31use futures::channel::{mpsc, oneshot};
  32use futures::{FutureExt, future::Shared};
  33use gpui::{
  34    App, AppContext, AsyncApp, BackgroundExecutor, Context, Entity, EventEmitter, SharedString,
  35    Task, WeakEntity,
  36};
  37
  38use serde_json::Value;
  39use smol::stream::StreamExt;
  40use std::any::TypeId;
  41use std::collections::BTreeMap;
  42use std::u64;
  43use std::{
  44    any::Any,
  45    collections::hash_map::Entry,
  46    hash::{Hash, Hasher},
  47    path::Path,
  48    sync::Arc,
  49};
  50use text::{PointUtf16, ToPointUtf16};
  51use util::ResultExt;
  52use worktree::Worktree;
  53
  54#[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)]
  55#[repr(transparent)]
  56pub struct ThreadId(pub u64);
  57
  58impl ThreadId {
  59    pub const MIN: ThreadId = ThreadId(u64::MIN);
  60    pub const MAX: ThreadId = ThreadId(u64::MAX);
  61}
  62
  63impl From<u64> for ThreadId {
  64    fn from(id: u64) -> Self {
  65        Self(id)
  66    }
  67}
  68
  69#[derive(Clone, Debug)]
  70pub struct StackFrame {
  71    pub dap: dap::StackFrame,
  72    pub scopes: Vec<dap::Scope>,
  73}
  74
  75impl From<dap::StackFrame> for StackFrame {
  76    fn from(stack_frame: dap::StackFrame) -> Self {
  77        Self {
  78            scopes: vec![],
  79            dap: stack_frame,
  80        }
  81    }
  82}
  83
  84#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
  85pub enum ThreadStatus {
  86    #[default]
  87    Running,
  88    Stopped,
  89    Stepping,
  90    Exited,
  91    Ended,
  92}
  93
  94impl ThreadStatus {
  95    pub fn label(&self) -> &'static str {
  96        match self {
  97            ThreadStatus::Running => "Running",
  98            ThreadStatus::Stopped => "Stopped",
  99            ThreadStatus::Stepping => "Stepping",
 100            ThreadStatus::Exited => "Exited",
 101            ThreadStatus::Ended => "Ended",
 102        }
 103    }
 104}
 105
 106#[derive(Debug)]
 107pub struct Thread {
 108    dap: dap::Thread,
 109    stack_frames: Vec<StackFrame>,
 110    _has_stopped: bool,
 111}
 112
 113impl From<dap::Thread> for Thread {
 114    fn from(dap: dap::Thread) -> Self {
 115        Self {
 116            dap,
 117            stack_frames: Default::default(),
 118            _has_stopped: false,
 119        }
 120    }
 121}
 122
 123pub enum Mode {
 124    Building,
 125    Running(RunningMode),
 126}
 127
 128#[derive(Clone)]
 129pub struct RunningMode {
 130    client: Arc<DebugAdapterClient>,
 131    binary: DebugAdapterBinary,
 132    tmp_breakpoint: Option<SourceBreakpoint>,
 133    worktree: WeakEntity<Worktree>,
 134    executor: BackgroundExecutor,
 135    is_started: bool,
 136}
 137
 138fn client_source(abs_path: &Path) -> dap::Source {
 139    dap::Source {
 140        name: abs_path
 141            .file_name()
 142            .map(|filename| filename.to_string_lossy().to_string()),
 143        path: Some(abs_path.to_string_lossy().to_string()),
 144        source_reference: None,
 145        presentation_hint: None,
 146        origin: None,
 147        sources: None,
 148        adapter_data: None,
 149        checksums: None,
 150    }
 151}
 152
 153impl RunningMode {
 154    async fn new(
 155        session_id: SessionId,
 156        parent_session: Option<Entity<Session>>,
 157        worktree: WeakEntity<Worktree>,
 158        binary: DebugAdapterBinary,
 159        messages_tx: futures::channel::mpsc::UnboundedSender<Message>,
 160        cx: AsyncApp,
 161    ) -> Result<Self> {
 162        let message_handler = Box::new(move |message| {
 163            messages_tx.unbounded_send(message).ok();
 164        });
 165
 166        let client = Arc::new(
 167            if let Some(client) = parent_session
 168                .and_then(|session| cx.update(|cx| session.read(cx).adapter_client()).ok())
 169                .flatten()
 170            {
 171                client
 172                    .reconnect(session_id, binary.clone(), message_handler, cx.clone())
 173                    .await?
 174            } else {
 175                DebugAdapterClient::start(session_id, binary.clone(), message_handler, cx.clone())
 176                    .await?
 177            },
 178        );
 179
 180        Ok(Self {
 181            client,
 182            worktree,
 183            tmp_breakpoint: None,
 184            binary,
 185            executor: cx.background_executor().clone(),
 186            is_started: false,
 187        })
 188    }
 189
 190    pub(crate) fn worktree(&self) -> &WeakEntity<Worktree> {
 191        &self.worktree
 192    }
 193
 194    fn unset_breakpoints_from_paths(&self, paths: &Vec<Arc<Path>>, cx: &mut App) -> Task<()> {
 195        let tasks: Vec<_> = paths
 196            .into_iter()
 197            .map(|path| {
 198                self.request(dap_command::SetBreakpoints {
 199                    source: client_source(path),
 200                    source_modified: None,
 201                    breakpoints: vec![],
 202                })
 203            })
 204            .collect();
 205
 206        cx.background_spawn(async move {
 207            futures::future::join_all(tasks)
 208                .await
 209                .iter()
 210                .for_each(|res| match res {
 211                    Ok(_) => {}
 212                    Err(err) => {
 213                        log::warn!("Set breakpoints request failed: {}", err);
 214                    }
 215                });
 216        })
 217    }
 218
 219    fn send_breakpoints_from_path(
 220        &self,
 221        abs_path: Arc<Path>,
 222        reason: BreakpointUpdatedReason,
 223        breakpoint_store: &Entity<BreakpointStore>,
 224        cx: &mut App,
 225    ) -> Task<()> {
 226        let breakpoints =
 227            breakpoint_store
 228                .read(cx)
 229                .source_breakpoints_from_path(&abs_path, cx)
 230                .into_iter()
 231                .filter(|bp| bp.state.is_enabled())
 232                .chain(self.tmp_breakpoint.iter().filter_map(|breakpoint| {
 233                    breakpoint.path.eq(&abs_path).then(|| breakpoint.clone())
 234                }))
 235                .map(Into::into)
 236                .collect();
 237
 238        let raw_breakpoints = breakpoint_store
 239            .read(cx)
 240            .breakpoints_from_path(&abs_path)
 241            .into_iter()
 242            .filter(|bp| bp.bp.state.is_enabled())
 243            .collect::<Vec<_>>();
 244
 245        let task = self.request(dap_command::SetBreakpoints {
 246            source: client_source(&abs_path),
 247            source_modified: Some(matches!(reason, BreakpointUpdatedReason::FileSaved)),
 248            breakpoints,
 249        });
 250        let session_id = self.client.id();
 251        let breakpoint_store = breakpoint_store.downgrade();
 252        cx.spawn(async move |cx| match cx.background_spawn(task).await {
 253            Ok(breakpoints) => {
 254                let breakpoints =
 255                    breakpoints
 256                        .into_iter()
 257                        .zip(raw_breakpoints)
 258                        .filter_map(|(dap_bp, zed_bp)| {
 259                            Some((
 260                                zed_bp,
 261                                BreakpointSessionState {
 262                                    id: dap_bp.id?,
 263                                    verified: dap_bp.verified,
 264                                },
 265                            ))
 266                        });
 267                breakpoint_store
 268                    .update(cx, |this, _| {
 269                        this.mark_breakpoints_verified(session_id, &abs_path, breakpoints);
 270                    })
 271                    .ok();
 272            }
 273            Err(err) => log::warn!("Set breakpoints request failed for path: {}", err),
 274        })
 275    }
 276
 277    fn send_exception_breakpoints(
 278        &self,
 279        filters: Vec<ExceptionBreakpointsFilter>,
 280        supports_filter_options: bool,
 281    ) -> Task<Result<Vec<dap::Breakpoint>>> {
 282        let arg = if supports_filter_options {
 283            SetExceptionBreakpoints::WithOptions {
 284                filters: filters
 285                    .into_iter()
 286                    .map(|filter| ExceptionFilterOptions {
 287                        filter_id: filter.filter,
 288                        condition: None,
 289                        mode: None,
 290                    })
 291                    .collect(),
 292            }
 293        } else {
 294            SetExceptionBreakpoints::Plain {
 295                filters: filters.into_iter().map(|filter| filter.filter).collect(),
 296            }
 297        };
 298        self.request(arg)
 299    }
 300
 301    fn send_source_breakpoints(
 302        &self,
 303        ignore_breakpoints: bool,
 304        breakpoint_store: &Entity<BreakpointStore>,
 305        cx: &App,
 306    ) -> Task<HashMap<Arc<Path>, anyhow::Error>> {
 307        let mut breakpoint_tasks = Vec::new();
 308        let breakpoints = breakpoint_store.read(cx).all_source_breakpoints(cx);
 309        let mut raw_breakpoints = breakpoint_store.read_with(cx, |this, _| this.all_breakpoints());
 310        debug_assert_eq!(raw_breakpoints.len(), breakpoints.len());
 311        let session_id = self.client.id();
 312        for (path, breakpoints) in breakpoints {
 313            let breakpoints = if ignore_breakpoints {
 314                vec![]
 315            } else {
 316                breakpoints
 317                    .into_iter()
 318                    .filter(|bp| bp.state.is_enabled())
 319                    .map(Into::into)
 320                    .collect()
 321            };
 322
 323            let raw_breakpoints = raw_breakpoints
 324                .remove(&path)
 325                .unwrap_or_default()
 326                .into_iter()
 327                .filter(|bp| bp.bp.state.is_enabled());
 328            let error_path = path.clone();
 329            let send_request = self
 330                .request(dap_command::SetBreakpoints {
 331                    source: client_source(&path),
 332                    source_modified: Some(false),
 333                    breakpoints,
 334                })
 335                .map(|result| result.map_err(move |e| (error_path, e)));
 336
 337            let task = cx.spawn({
 338                let breakpoint_store = breakpoint_store.downgrade();
 339                async move |cx| {
 340                    let breakpoints = cx.background_spawn(send_request).await?;
 341
 342                    let breakpoints = breakpoints.into_iter().zip(raw_breakpoints).filter_map(
 343                        |(dap_bp, zed_bp)| {
 344                            Some((
 345                                zed_bp,
 346                                BreakpointSessionState {
 347                                    id: dap_bp.id?,
 348                                    verified: dap_bp.verified,
 349                                },
 350                            ))
 351                        },
 352                    );
 353                    breakpoint_store
 354                        .update(cx, |this, _| {
 355                            this.mark_breakpoints_verified(session_id, &path, breakpoints);
 356                        })
 357                        .ok();
 358
 359                    Ok(())
 360                }
 361            });
 362            breakpoint_tasks.push(task);
 363        }
 364
 365        cx.background_spawn(async move {
 366            futures::future::join_all(breakpoint_tasks)
 367                .await
 368                .into_iter()
 369                .filter_map(Result::err)
 370                .collect::<HashMap<_, _>>()
 371        })
 372    }
 373
 374    fn initialize_sequence(
 375        &self,
 376        capabilities: &Capabilities,
 377        initialized_rx: oneshot::Receiver<()>,
 378        dap_store: WeakEntity<DapStore>,
 379        cx: &mut Context<Session>,
 380    ) -> Task<Result<()>> {
 381        let raw = self.binary.request_args.clone();
 382
 383        // Of relevance: https://github.com/microsoft/vscode/issues/4902#issuecomment-368583522
 384        let launch = match raw.request {
 385            dap::StartDebuggingRequestArgumentsRequest::Launch => self.request(Launch {
 386                raw: raw.configuration,
 387            }),
 388            dap::StartDebuggingRequestArgumentsRequest::Attach => self.request(Attach {
 389                raw: raw.configuration,
 390            }),
 391        };
 392
 393        let configuration_done_supported = ConfigurationDone::is_supported(capabilities);
 394        let exception_filters = capabilities
 395            .exception_breakpoint_filters
 396            .as_ref()
 397            .map(|exception_filters| {
 398                exception_filters
 399                    .iter()
 400                    .filter(|filter| filter.default == Some(true))
 401                    .cloned()
 402                    .collect::<Vec<_>>()
 403            })
 404            .unwrap_or_default();
 405        let supports_exception_filters = capabilities
 406            .supports_exception_filter_options
 407            .unwrap_or_default();
 408        let this = self.clone();
 409        let worktree = self.worktree().clone();
 410        let configuration_sequence = cx.spawn({
 411            async move |_, cx| {
 412                let breakpoint_store =
 413                    dap_store.read_with(cx, |dap_store, _| dap_store.breakpoint_store().clone())?;
 414                initialized_rx.await?;
 415                let errors_by_path = cx
 416                    .update(|cx| this.send_source_breakpoints(false, &breakpoint_store, cx))?
 417                    .await;
 418
 419                dap_store.update(cx, |_, cx| {
 420                    let Some(worktree) = worktree.upgrade() else {
 421                        return;
 422                    };
 423
 424                    for (path, error) in &errors_by_path {
 425                        log::error!("failed to set breakpoints for {path:?}: {error}");
 426                    }
 427
 428                    if let Some(failed_path) = errors_by_path.keys().next() {
 429                        let failed_path = failed_path
 430                            .strip_prefix(worktree.read(cx).abs_path())
 431                            .unwrap_or(failed_path)
 432                            .display();
 433                        let message = format!(
 434                            "Failed to set breakpoints for {failed_path}{}",
 435                            match errors_by_path.len() {
 436                                0 => unreachable!(),
 437                                1 => "".into(),
 438                                2 => " and 1 other path".into(),
 439                                n => format!(" and {} other paths", n - 1),
 440                            }
 441                        );
 442                        cx.emit(super::dap_store::DapStoreEvent::Notification(message));
 443                    }
 444                })?;
 445
 446                this.send_exception_breakpoints(exception_filters, supports_exception_filters)
 447                    .await
 448                    .ok();
 449                let ret = if configuration_done_supported {
 450                    this.request(ConfigurationDone {})
 451                } else {
 452                    Task::ready(Ok(()))
 453                }
 454                .await;
 455                ret
 456            }
 457        });
 458
 459        let task = cx.background_spawn(futures::future::try_join(launch, configuration_sequence));
 460
 461        cx.spawn(async move |this, cx| {
 462            let result = task.await;
 463
 464            this.update(cx, |this, cx| {
 465                if let Some(this) = this.as_running_mut() {
 466                    this.is_started = true;
 467                    cx.notify();
 468                }
 469            })
 470            .ok();
 471
 472            result?;
 473            anyhow::Ok(())
 474        })
 475    }
 476
 477    fn request<R: LocalDapCommand>(&self, request: R) -> Task<Result<R::Response>>
 478    where
 479        <R::DapRequest as dap::requests::Request>::Response: 'static,
 480        <R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
 481    {
 482        let request = Arc::new(request);
 483
 484        let request_clone = request.clone();
 485        let connection = self.client.clone();
 486        self.executor.spawn(async move {
 487            let args = request_clone.to_dap();
 488            let response = connection.request::<R::DapRequest>(args).await?;
 489            request.response_from_dap(response)
 490        })
 491    }
 492}
 493
 494impl Mode {
 495    pub(super) fn request_dap<R: DapCommand>(&self, request: R) -> Task<Result<R::Response>>
 496    where
 497        <R::DapRequest as dap::requests::Request>::Response: 'static,
 498        <R::DapRequest as dap::requests::Request>::Arguments: 'static + Send,
 499    {
 500        match self {
 501            Mode::Running(debug_adapter_client) => debug_adapter_client.request(request),
 502            Mode::Building => Task::ready(Err(anyhow!(
 503                "no adapter running to send request: {request:?}"
 504            ))),
 505        }
 506    }
 507}
 508
 509#[derive(Default)]
 510struct ThreadStates {
 511    global_state: Option<ThreadStatus>,
 512    known_thread_states: IndexMap<ThreadId, ThreadStatus>,
 513}
 514
 515impl ThreadStates {
 516    fn stop_all_threads(&mut self) {
 517        self.global_state = Some(ThreadStatus::Stopped);
 518        self.known_thread_states.clear();
 519    }
 520
 521    fn exit_all_threads(&mut self) {
 522        self.global_state = Some(ThreadStatus::Exited);
 523        self.known_thread_states.clear();
 524    }
 525
 526    fn continue_all_threads(&mut self) {
 527        self.global_state = Some(ThreadStatus::Running);
 528        self.known_thread_states.clear();
 529    }
 530
 531    fn stop_thread(&mut self, thread_id: ThreadId) {
 532        self.known_thread_states
 533            .insert(thread_id, ThreadStatus::Stopped);
 534    }
 535
 536    fn continue_thread(&mut self, thread_id: ThreadId) {
 537        self.known_thread_states
 538            .insert(thread_id, ThreadStatus::Running);
 539    }
 540
 541    fn process_step(&mut self, thread_id: ThreadId) {
 542        self.known_thread_states
 543            .insert(thread_id, ThreadStatus::Stepping);
 544    }
 545
 546    fn thread_status(&self, thread_id: ThreadId) -> ThreadStatus {
 547        self.thread_state(thread_id)
 548            .unwrap_or(ThreadStatus::Running)
 549    }
 550
 551    fn thread_state(&self, thread_id: ThreadId) -> Option<ThreadStatus> {
 552        self.known_thread_states
 553            .get(&thread_id)
 554            .copied()
 555            .or(self.global_state)
 556    }
 557
 558    fn exit_thread(&mut self, thread_id: ThreadId) {
 559        self.known_thread_states
 560            .insert(thread_id, ThreadStatus::Exited);
 561    }
 562
 563    fn any_stopped_thread(&self) -> bool {
 564        self.global_state
 565            .is_some_and(|state| state == ThreadStatus::Stopped)
 566            || self
 567                .known_thread_states
 568                .values()
 569                .any(|status| *status == ThreadStatus::Stopped)
 570    }
 571}
 572const MAX_TRACKED_OUTPUT_EVENTS: usize = 5000;
 573
 574type IsEnabled = bool;
 575
 576#[derive(Copy, Clone, Default, Debug, PartialEq, PartialOrd, Eq, Ord)]
 577pub struct OutputToken(pub usize);
 578/// Represents a current state of a single debug adapter and provides ways to mutate it.
 579pub struct Session {
 580    pub mode: Mode,
 581    id: SessionId,
 582    label: SharedString,
 583    adapter: DebugAdapterName,
 584    pub(super) capabilities: Capabilities,
 585    child_session_ids: HashSet<SessionId>,
 586    parent_session: Option<Entity<Session>>,
 587    modules: Vec<dap::Module>,
 588    loaded_sources: Vec<dap::Source>,
 589    output_token: OutputToken,
 590    output: Box<circular_buffer::CircularBuffer<MAX_TRACKED_OUTPUT_EVENTS, dap::OutputEvent>>,
 591    threads: IndexMap<ThreadId, Thread>,
 592    thread_states: ThreadStates,
 593    variables: HashMap<VariableReference, Vec<dap::Variable>>,
 594    stack_frames: IndexMap<StackFrameId, StackFrame>,
 595    locations: HashMap<u64, dap::LocationsResponse>,
 596    is_session_terminated: bool,
 597    requests: HashMap<TypeId, HashMap<RequestSlot, Shared<Task<Option<()>>>>>,
 598    pub(crate) breakpoint_store: Entity<BreakpointStore>,
 599    ignore_breakpoints: bool,
 600    exception_breakpoints: BTreeMap<String, (ExceptionBreakpointsFilter, IsEnabled)>,
 601    background_tasks: Vec<Task<()>>,
 602}
 603
 604trait CacheableCommand: Any + Send + Sync {
 605    fn dyn_eq(&self, rhs: &dyn CacheableCommand) -> bool;
 606    fn dyn_hash(&self, hasher: &mut dyn Hasher);
 607    fn as_any_arc(self: Arc<Self>) -> Arc<dyn Any + Send + Sync>;
 608}
 609
 610impl<T> CacheableCommand for T
 611where
 612    T: DapCommand + PartialEq + Eq + Hash,
 613{
 614    fn dyn_eq(&self, rhs: &dyn CacheableCommand) -> bool {
 615        (rhs as &dyn Any)
 616            .downcast_ref::<Self>()
 617            .map_or(false, |rhs| self == rhs)
 618    }
 619
 620    fn dyn_hash(&self, mut hasher: &mut dyn Hasher) {
 621        T::hash(self, &mut hasher);
 622    }
 623
 624    fn as_any_arc(self: Arc<Self>) -> Arc<dyn Any + Send + Sync> {
 625        self
 626    }
 627}
 628
 629pub(crate) struct RequestSlot(Arc<dyn CacheableCommand>);
 630
 631impl<T: DapCommand + PartialEq + Eq + Hash> From<T> for RequestSlot {
 632    fn from(request: T) -> Self {
 633        Self(Arc::new(request))
 634    }
 635}
 636
 637impl PartialEq for RequestSlot {
 638    fn eq(&self, other: &Self) -> bool {
 639        self.0.dyn_eq(other.0.as_ref())
 640    }
 641}
 642
 643impl Eq for RequestSlot {}
 644
 645impl Hash for RequestSlot {
 646    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
 647        self.0.dyn_hash(state);
 648        (&*self.0 as &dyn Any).type_id().hash(state)
 649    }
 650}
 651
 652#[derive(Debug, Clone, Hash, PartialEq, Eq)]
 653pub struct CompletionsQuery {
 654    pub query: String,
 655    pub column: u64,
 656    pub line: Option<u64>,
 657    pub frame_id: Option<u64>,
 658}
 659
 660impl CompletionsQuery {
 661    pub fn new(
 662        buffer: &language::Buffer,
 663        cursor_position: language::Anchor,
 664        frame_id: Option<u64>,
 665    ) -> Self {
 666        let PointUtf16 { row, column } = cursor_position.to_point_utf16(&buffer.snapshot());
 667        Self {
 668            query: buffer.text(),
 669            column: column as u64,
 670            frame_id,
 671            line: Some(row as u64),
 672        }
 673    }
 674}
 675
 676#[derive(Debug)]
 677pub enum SessionEvent {
 678    Modules,
 679    LoadedSources,
 680    Stopped(Option<ThreadId>),
 681    StackTrace,
 682    Variables,
 683    Threads,
 684    InvalidateInlineValue,
 685    CapabilitiesLoaded,
 686    RunInTerminal {
 687        request: RunInTerminalRequestArguments,
 688        sender: mpsc::Sender<Result<u32>>,
 689    },
 690    ConsoleOutput,
 691}
 692
 693#[derive(Clone, Debug, PartialEq, Eq)]
 694pub enum SessionStateEvent {
 695    Running,
 696    Shutdown,
 697    Restart,
 698    SpawnChildSession {
 699        request: StartDebuggingRequestArguments,
 700    },
 701}
 702
 703impl EventEmitter<SessionEvent> for Session {}
 704impl EventEmitter<SessionStateEvent> for Session {}
 705
 706// local session will send breakpoint updates to DAP for all new breakpoints
 707// remote side will only send breakpoint updates when it is a breakpoint created by that peer
 708// BreakpointStore notifies session on breakpoint changes
 709impl Session {
 710    pub(crate) fn new(
 711        breakpoint_store: Entity<BreakpointStore>,
 712        session_id: SessionId,
 713        parent_session: Option<Entity<Session>>,
 714        label: SharedString,
 715        adapter: DebugAdapterName,
 716        cx: &mut App,
 717    ) -> Entity<Self> {
 718        cx.new::<Self>(|cx| {
 719            cx.subscribe(&breakpoint_store, |this, store, event, cx| match event {
 720                BreakpointStoreEvent::BreakpointsUpdated(path, reason) => {
 721                    if let Some(local) = (!this.ignore_breakpoints)
 722                        .then(|| this.as_running_mut())
 723                        .flatten()
 724                    {
 725                        local
 726                            .send_breakpoints_from_path(path.clone(), *reason, &store, cx)
 727                            .detach();
 728                    };
 729                }
 730                BreakpointStoreEvent::BreakpointsCleared(paths) => {
 731                    if let Some(local) = (!this.ignore_breakpoints)
 732                        .then(|| this.as_running_mut())
 733                        .flatten()
 734                    {
 735                        local.unset_breakpoints_from_paths(paths, cx).detach();
 736                    }
 737                }
 738                BreakpointStoreEvent::SetDebugLine | BreakpointStoreEvent::ClearDebugLines => {}
 739            })
 740            .detach();
 741            cx.on_app_quit(Self::on_app_quit).detach();
 742
 743            let this = Self {
 744                mode: Mode::Building,
 745                id: session_id,
 746                child_session_ids: HashSet::default(),
 747                parent_session,
 748                capabilities: Capabilities::default(),
 749                variables: Default::default(),
 750                stack_frames: Default::default(),
 751                thread_states: ThreadStates::default(),
 752                output_token: OutputToken(0),
 753                output: circular_buffer::CircularBuffer::boxed(),
 754                requests: HashMap::default(),
 755                modules: Vec::default(),
 756                loaded_sources: Vec::default(),
 757                threads: IndexMap::default(),
 758                background_tasks: Vec::default(),
 759                locations: Default::default(),
 760                is_session_terminated: false,
 761                ignore_breakpoints: false,
 762                breakpoint_store,
 763                exception_breakpoints: Default::default(),
 764                label,
 765                adapter,
 766            };
 767
 768            this
 769        })
 770    }
 771
 772    pub fn worktree(&self) -> Option<Entity<Worktree>> {
 773        match &self.mode {
 774            Mode::Building => None,
 775            Mode::Running(local_mode) => local_mode.worktree.upgrade(),
 776        }
 777    }
 778
 779    pub fn boot(
 780        &mut self,
 781        binary: DebugAdapterBinary,
 782        worktree: Entity<Worktree>,
 783        dap_store: WeakEntity<DapStore>,
 784        cx: &mut Context<Self>,
 785    ) -> Task<Result<()>> {
 786        let (message_tx, mut message_rx) = futures::channel::mpsc::unbounded();
 787        let (initialized_tx, initialized_rx) = futures::channel::oneshot::channel();
 788
 789        let background_tasks = vec![cx.spawn(async move |this: WeakEntity<Session>, cx| {
 790            let mut initialized_tx = Some(initialized_tx);
 791            while let Some(message) = message_rx.next().await {
 792                if let Message::Event(event) = message {
 793                    if let Events::Initialized(_) = *event {
 794                        if let Some(tx) = initialized_tx.take() {
 795                            tx.send(()).ok();
 796                        }
 797                    } else {
 798                        let Ok(_) = this.update(cx, |session, cx| {
 799                            session.handle_dap_event(event, cx);
 800                        }) else {
 801                            break;
 802                        };
 803                    }
 804                } else if let Message::Request(request) = message {
 805                    let Ok(_) = this.update(cx, |this, cx| {
 806                        if request.command == StartDebugging::COMMAND {
 807                            this.handle_start_debugging_request(request, cx)
 808                                .detach_and_log_err(cx);
 809                        } else if request.command == RunInTerminal::COMMAND {
 810                            this.handle_run_in_terminal_request(request, cx)
 811                                .detach_and_log_err(cx);
 812                        }
 813                    }) else {
 814                        break;
 815                    };
 816                }
 817            }
 818        })];
 819        self.background_tasks = background_tasks;
 820        let id = self.id;
 821        let parent_session = self.parent_session.clone();
 822
 823        cx.spawn(async move |this, cx| {
 824            let mode = RunningMode::new(
 825                id,
 826                parent_session,
 827                worktree.downgrade(),
 828                binary.clone(),
 829                message_tx,
 830                cx.clone(),
 831            )
 832            .await?;
 833            this.update(cx, |this, cx| {
 834                this.mode = Mode::Running(mode);
 835                cx.emit(SessionStateEvent::Running);
 836            })?;
 837
 838            this.update(cx, |session, cx| session.request_initialize(cx))?
 839                .await?;
 840
 841            let result = this
 842                .update(cx, |session, cx| {
 843                    session.initialize_sequence(initialized_rx, dap_store.clone(), cx)
 844                })?
 845                .await;
 846
 847            if result.is_err() {
 848                let mut console = this.update(cx, |session, cx| session.console_output(cx))?;
 849
 850                console
 851                    .send(format!(
 852                        "Tried to launch debugger with: {}",
 853                        serde_json::to_string_pretty(&binary.request_args.configuration)
 854                            .unwrap_or_default(),
 855                    ))
 856                    .await
 857                    .ok();
 858            }
 859
 860            result
 861        })
 862    }
 863
 864    pub fn session_id(&self) -> SessionId {
 865        self.id
 866    }
 867
 868    pub fn child_session_ids(&self) -> HashSet<SessionId> {
 869        self.child_session_ids.clone()
 870    }
 871
 872    pub fn add_child_session_id(&mut self, session_id: SessionId) {
 873        self.child_session_ids.insert(session_id);
 874    }
 875
 876    pub fn remove_child_session_id(&mut self, session_id: SessionId) {
 877        self.child_session_ids.remove(&session_id);
 878    }
 879
 880    pub fn parent_id(&self, cx: &App) -> Option<SessionId> {
 881        self.parent_session
 882            .as_ref()
 883            .map(|session| session.read(cx).id)
 884    }
 885
 886    pub fn parent_session(&self) -> Option<&Entity<Self>> {
 887        self.parent_session.as_ref()
 888    }
 889
 890    pub fn capabilities(&self) -> &Capabilities {
 891        &self.capabilities
 892    }
 893
 894    pub fn binary(&self) -> &DebugAdapterBinary {
 895        let Mode::Running(local_mode) = &self.mode else {
 896            panic!("Session is not running");
 897        };
 898        &local_mode.binary
 899    }
 900
 901    pub fn adapter(&self) -> DebugAdapterName {
 902        self.adapter.clone()
 903    }
 904
 905    pub fn label(&self) -> SharedString {
 906        self.label.clone()
 907    }
 908
 909    pub fn is_terminated(&self) -> bool {
 910        self.is_session_terminated
 911    }
 912
 913    pub fn console_output(&mut self, cx: &mut Context<Self>) -> mpsc::UnboundedSender<String> {
 914        let (tx, mut rx) = mpsc::unbounded();
 915
 916        cx.spawn(async move |this, cx| {
 917            while let Some(output) = rx.next().await {
 918                this.update(cx, |this, cx| {
 919                    let event = dap::OutputEvent {
 920                        category: None,
 921                        output,
 922                        group: None,
 923                        variables_reference: None,
 924                        source: None,
 925                        line: None,
 926                        column: None,
 927                        data: None,
 928                        location_reference: None,
 929                    };
 930                    this.push_output(event, cx);
 931                })?;
 932            }
 933            anyhow::Ok(())
 934        })
 935        .detach();
 936
 937        return tx;
 938    }
 939
 940    pub fn is_started(&self) -> bool {
 941        match &self.mode {
 942            Mode::Building => false,
 943            Mode::Running(running) => running.is_started,
 944        }
 945    }
 946
 947    pub fn is_building(&self) -> bool {
 948        matches!(self.mode, Mode::Building)
 949    }
 950
 951    pub fn is_running(&self) -> bool {
 952        matches!(self.mode, Mode::Running(_))
 953    }
 954
 955    pub fn as_running_mut(&mut self) -> Option<&mut RunningMode> {
 956        match &mut self.mode {
 957            Mode::Running(local_mode) => Some(local_mode),
 958            Mode::Building => None,
 959        }
 960    }
 961
 962    pub fn as_running(&self) -> Option<&RunningMode> {
 963        match &self.mode {
 964            Mode::Running(local_mode) => Some(local_mode),
 965            Mode::Building => None,
 966        }
 967    }
 968
 969    fn handle_start_debugging_request(
 970        &mut self,
 971        request: dap::messages::Request,
 972        cx: &mut Context<Self>,
 973    ) -> Task<Result<()>> {
 974        let request_seq = request.seq;
 975
 976        let launch_request: Option<Result<StartDebuggingRequestArguments, _>> = request
 977            .arguments
 978            .as_ref()
 979            .map(|value| serde_json::from_value(value.clone()));
 980
 981        let mut success = true;
 982        if let Some(Ok(request)) = launch_request {
 983            cx.emit(SessionStateEvent::SpawnChildSession { request });
 984        } else {
 985            log::error!(
 986                "Failed to parse launch request arguments: {:?}",
 987                request.arguments
 988            );
 989            success = false;
 990        }
 991
 992        cx.spawn(async move |this, cx| {
 993            this.update(cx, |this, cx| {
 994                this.respond_to_client(
 995                    request_seq,
 996                    success,
 997                    StartDebugging::COMMAND.to_string(),
 998                    None,
 999                    cx,
1000                )
1001            })?
1002            .await
1003        })
1004    }
1005
1006    fn handle_run_in_terminal_request(
1007        &mut self,
1008        request: dap::messages::Request,
1009        cx: &mut Context<Self>,
1010    ) -> Task<Result<()>> {
1011        let request_args = match serde_json::from_value::<RunInTerminalRequestArguments>(
1012            request.arguments.unwrap_or_default(),
1013        ) {
1014            Ok(args) => args,
1015            Err(error) => {
1016                return cx.spawn(async move |session, cx| {
1017                    let error = serde_json::to_value(dap::ErrorResponse {
1018                        error: Some(dap::Message {
1019                            id: request.seq,
1020                            format: error.to_string(),
1021                            variables: None,
1022                            send_telemetry: None,
1023                            show_user: None,
1024                            url: None,
1025                            url_label: None,
1026                        }),
1027                    })
1028                    .ok();
1029
1030                    session
1031                        .update(cx, |this, cx| {
1032                            this.respond_to_client(
1033                                request.seq,
1034                                false,
1035                                StartDebugging::COMMAND.to_string(),
1036                                error,
1037                                cx,
1038                            )
1039                        })?
1040                        .await?;
1041
1042                    Err(anyhow!("Failed to parse RunInTerminalRequestArguments"))
1043                });
1044            }
1045        };
1046
1047        let seq = request.seq;
1048
1049        let (tx, mut rx) = mpsc::channel::<Result<u32>>(1);
1050        cx.emit(SessionEvent::RunInTerminal {
1051            request: request_args,
1052            sender: tx,
1053        });
1054        cx.notify();
1055
1056        cx.spawn(async move |session, cx| {
1057            let result = util::maybe!(async move {
1058                rx.next().await.ok_or_else(|| {
1059                    anyhow!("failed to receive response from spawn terminal".to_string())
1060                })?
1061            })
1062            .await;
1063            let (success, body) = match result {
1064                Ok(pid) => (
1065                    true,
1066                    serde_json::to_value(dap::RunInTerminalResponse {
1067                        process_id: None,
1068                        shell_process_id: Some(pid as u64),
1069                    })
1070                    .ok(),
1071                ),
1072                Err(error) => (
1073                    false,
1074                    serde_json::to_value(dap::ErrorResponse {
1075                        error: Some(dap::Message {
1076                            id: seq,
1077                            format: error.to_string(),
1078                            variables: None,
1079                            send_telemetry: None,
1080                            show_user: None,
1081                            url: None,
1082                            url_label: None,
1083                        }),
1084                    })
1085                    .ok(),
1086                ),
1087            };
1088
1089            session
1090                .update(cx, |session, cx| {
1091                    session.respond_to_client(
1092                        seq,
1093                        success,
1094                        RunInTerminal::COMMAND.to_string(),
1095                        body,
1096                        cx,
1097                    )
1098                })?
1099                .await
1100        })
1101    }
1102
1103    pub(super) fn request_initialize(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
1104        let adapter_id = self.adapter().to_string();
1105        let request = Initialize { adapter_id };
1106        match &self.mode {
1107            Mode::Running(local_mode) => {
1108                let capabilities = local_mode.request(request);
1109
1110                cx.spawn(async move |this, cx| {
1111                    let capabilities = capabilities.await?;
1112                    this.update(cx, |session, cx| {
1113                        session.capabilities = capabilities;
1114                        let filters = session
1115                            .capabilities
1116                            .exception_breakpoint_filters
1117                            .clone()
1118                            .unwrap_or_default();
1119                        for filter in filters {
1120                            let default = filter.default.unwrap_or_default();
1121                            session
1122                                .exception_breakpoints
1123                                .entry(filter.filter.clone())
1124                                .or_insert_with(|| (filter, default));
1125                        }
1126                        cx.emit(SessionEvent::CapabilitiesLoaded);
1127                    })?;
1128                    Ok(())
1129                })
1130            }
1131            Mode::Building => Task::ready(Err(anyhow!(
1132                "Cannot send initialize request, task still building"
1133            ))),
1134        }
1135    }
1136
1137    pub(super) fn initialize_sequence(
1138        &mut self,
1139        initialize_rx: oneshot::Receiver<()>,
1140        dap_store: WeakEntity<DapStore>,
1141        cx: &mut Context<Self>,
1142    ) -> Task<Result<()>> {
1143        match &self.mode {
1144            Mode::Running(local_mode) => {
1145                local_mode.initialize_sequence(&self.capabilities, initialize_rx, dap_store, cx)
1146            }
1147            Mode::Building => Task::ready(Err(anyhow!("cannot initialize, still building"))),
1148        }
1149    }
1150
1151    pub fn run_to_position(
1152        &mut self,
1153        breakpoint: SourceBreakpoint,
1154        active_thread_id: ThreadId,
1155        cx: &mut Context<Self>,
1156    ) {
1157        match &mut self.mode {
1158            Mode::Running(local_mode) => {
1159                if !matches!(
1160                    self.thread_states.thread_state(active_thread_id),
1161                    Some(ThreadStatus::Stopped)
1162                ) {
1163                    return;
1164                };
1165                let path = breakpoint.path.clone();
1166                local_mode.tmp_breakpoint = Some(breakpoint);
1167                let task = local_mode.send_breakpoints_from_path(
1168                    path,
1169                    BreakpointUpdatedReason::Toggled,
1170                    &self.breakpoint_store,
1171                    cx,
1172                );
1173
1174                cx.spawn(async move |this, cx| {
1175                    task.await;
1176                    this.update(cx, |this, cx| {
1177                        this.continue_thread(active_thread_id, cx);
1178                    })
1179                })
1180                .detach();
1181            }
1182            Mode::Building => {}
1183        }
1184    }
1185
1186    pub fn has_new_output(&self, last_update: OutputToken) -> bool {
1187        self.output_token.0.checked_sub(last_update.0).unwrap_or(0) != 0
1188    }
1189
1190    pub fn output(
1191        &self,
1192        since: OutputToken,
1193    ) -> (impl Iterator<Item = &dap::OutputEvent>, OutputToken) {
1194        if self.output_token.0 == 0 {
1195            return (self.output.range(0..0), OutputToken(0));
1196        };
1197
1198        let events_since = self.output_token.0.checked_sub(since.0).unwrap_or(0);
1199
1200        let clamped_events_since = events_since.clamp(0, self.output.len());
1201        (
1202            self.output
1203                .range(self.output.len() - clamped_events_since..),
1204            self.output_token,
1205        )
1206    }
1207
1208    pub fn respond_to_client(
1209        &self,
1210        request_seq: u64,
1211        success: bool,
1212        command: String,
1213        body: Option<serde_json::Value>,
1214        cx: &mut Context<Self>,
1215    ) -> Task<Result<()>> {
1216        let Some(local_session) = self.as_running() else {
1217            unreachable!("Cannot respond to remote client");
1218        };
1219        let client = local_session.client.clone();
1220
1221        cx.background_spawn(async move {
1222            client
1223                .send_message(Message::Response(Response {
1224                    body,
1225                    success,
1226                    command,
1227                    seq: request_seq + 1,
1228                    request_seq,
1229                    message: None,
1230                }))
1231                .await
1232        })
1233    }
1234
1235    fn handle_stopped_event(&mut self, event: StoppedEvent, cx: &mut Context<Self>) {
1236        // todo(debugger): Find a clean way to get around the clone
1237        let breakpoint_store = self.breakpoint_store.clone();
1238        if let Some((local, path)) = self.as_running_mut().and_then(|local| {
1239            let breakpoint = local.tmp_breakpoint.take()?;
1240            let path = breakpoint.path.clone();
1241            Some((local, path))
1242        }) {
1243            local
1244                .send_breakpoints_from_path(
1245                    path,
1246                    BreakpointUpdatedReason::Toggled,
1247                    &breakpoint_store,
1248                    cx,
1249                )
1250                .detach();
1251        };
1252
1253        if event.all_threads_stopped.unwrap_or_default() || event.thread_id.is_none() {
1254            self.thread_states.stop_all_threads();
1255
1256            self.invalidate_command_type::<StackTraceCommand>();
1257        }
1258
1259        // Event if we stopped all threads we still need to insert the thread_id
1260        // to our own data
1261        if let Some(thread_id) = event.thread_id {
1262            self.thread_states.stop_thread(ThreadId(thread_id));
1263
1264            self.invalidate_state(
1265                &StackTraceCommand {
1266                    thread_id,
1267                    start_frame: None,
1268                    levels: None,
1269                }
1270                .into(),
1271            );
1272        }
1273
1274        self.invalidate_generic();
1275        self.threads.clear();
1276        self.variables.clear();
1277        cx.emit(SessionEvent::Stopped(
1278            event
1279                .thread_id
1280                .map(Into::into)
1281                .filter(|_| !event.preserve_focus_hint.unwrap_or(false)),
1282        ));
1283        cx.emit(SessionEvent::InvalidateInlineValue);
1284        cx.notify();
1285    }
1286
1287    pub(crate) fn handle_dap_event(&mut self, event: Box<Events>, cx: &mut Context<Self>) {
1288        match *event {
1289            Events::Initialized(_) => {
1290                debug_assert!(
1291                    false,
1292                    "Initialized event should have been handled in LocalMode"
1293                );
1294            }
1295            Events::Stopped(event) => self.handle_stopped_event(event, cx),
1296            Events::Continued(event) => {
1297                if event.all_threads_continued.unwrap_or_default() {
1298                    self.thread_states.continue_all_threads();
1299                    self.breakpoint_store.update(cx, |store, cx| {
1300                        store.remove_active_position(Some(self.session_id()), cx)
1301                    });
1302                } else {
1303                    self.thread_states
1304                        .continue_thread(ThreadId(event.thread_id));
1305                }
1306                // todo(debugger): We should be able to get away with only invalidating generic if all threads were continued
1307                self.invalidate_generic();
1308            }
1309            Events::Exited(_event) => {
1310                self.clear_active_debug_line(cx);
1311            }
1312            Events::Terminated(_) => {
1313                self.shutdown(cx).detach();
1314            }
1315            Events::Thread(event) => {
1316                let thread_id = ThreadId(event.thread_id);
1317
1318                match event.reason {
1319                    dap::ThreadEventReason::Started => {
1320                        self.thread_states.continue_thread(thread_id);
1321                    }
1322                    dap::ThreadEventReason::Exited => {
1323                        self.thread_states.exit_thread(thread_id);
1324                    }
1325                    reason => {
1326                        log::error!("Unhandled thread event reason {:?}", reason);
1327                    }
1328                }
1329                self.invalidate_state(&ThreadsCommand.into());
1330                cx.notify();
1331            }
1332            Events::Output(event) => {
1333                if event
1334                    .category
1335                    .as_ref()
1336                    .is_some_and(|category| *category == OutputEventCategory::Telemetry)
1337                {
1338                    return;
1339                }
1340
1341                self.push_output(event, cx);
1342                cx.notify();
1343            }
1344            Events::Breakpoint(event) => self.breakpoint_store.update(cx, |store, _| {
1345                store.update_session_breakpoint(self.session_id(), event.reason, event.breakpoint);
1346            }),
1347            Events::Module(event) => {
1348                match event.reason {
1349                    dap::ModuleEventReason::New => {
1350                        self.modules.push(event.module);
1351                    }
1352                    dap::ModuleEventReason::Changed => {
1353                        if let Some(module) = self
1354                            .modules
1355                            .iter_mut()
1356                            .find(|other| event.module.id == other.id)
1357                        {
1358                            *module = event.module;
1359                        }
1360                    }
1361                    dap::ModuleEventReason::Removed => {
1362                        self.modules.retain(|other| event.module.id != other.id);
1363                    }
1364                }
1365
1366                // todo(debugger): We should only send the invalidate command to downstream clients.
1367                // self.invalidate_state(&ModulesCommand.into());
1368            }
1369            Events::LoadedSource(_) => {
1370                self.invalidate_state(&LoadedSourcesCommand.into());
1371            }
1372            Events::Capabilities(event) => {
1373                self.capabilities = self.capabilities.merge(event.capabilities);
1374                cx.notify();
1375            }
1376            Events::Memory(_) => {}
1377            Events::Process(_) => {}
1378            Events::ProgressEnd(_) => {}
1379            Events::ProgressStart(_) => {}
1380            Events::ProgressUpdate(_) => {}
1381            Events::Invalidated(_) => {}
1382            Events::Other(_) => {}
1383        }
1384    }
1385
1386    /// Ensure that there's a request in flight for the given command, and if not, send it. Use this to run requests that are idempotent.
1387    fn fetch<T: DapCommand + PartialEq + Eq + Hash>(
1388        &mut self,
1389        request: T,
1390        process_result: impl FnOnce(
1391            &mut Self,
1392            Result<T::Response>,
1393            &mut Context<Self>,
1394        ) -> Option<T::Response>
1395        + 'static,
1396        cx: &mut Context<Self>,
1397    ) {
1398        const {
1399            assert!(
1400                T::CACHEABLE,
1401                "Only requests marked as cacheable should invoke `fetch`"
1402            );
1403        }
1404
1405        if !self.thread_states.any_stopped_thread()
1406            && request.type_id() != TypeId::of::<ThreadsCommand>()
1407            || self.is_session_terminated
1408        {
1409            return;
1410        }
1411
1412        let request_map = self
1413            .requests
1414            .entry(std::any::TypeId::of::<T>())
1415            .or_default();
1416
1417        if let Entry::Vacant(vacant) = request_map.entry(request.into()) {
1418            let command = vacant.key().0.clone().as_any_arc().downcast::<T>().unwrap();
1419
1420            let task = Self::request_inner::<Arc<T>>(
1421                &self.capabilities,
1422                &self.mode,
1423                command,
1424                process_result,
1425                cx,
1426            );
1427            let task = cx
1428                .background_executor()
1429                .spawn(async move {
1430                    let _ = task.await?;
1431                    Some(())
1432                })
1433                .shared();
1434
1435            vacant.insert(task);
1436            cx.notify();
1437        }
1438    }
1439
1440    pub async fn request2<T: DapCommand + PartialEq + Eq + Hash>(
1441        &self,
1442        request: T,
1443    ) -> Result<T::Response> {
1444        if !T::is_supported(&self.capabilities) {
1445            anyhow::bail!("DAP request {:?} is not supported", request);
1446        }
1447
1448        self.mode.request_dap(request).await
1449    }
1450
1451    fn request_inner<T: DapCommand + PartialEq + Eq + Hash>(
1452        capabilities: &Capabilities,
1453        mode: &Mode,
1454        request: T,
1455        process_result: impl FnOnce(
1456            &mut Self,
1457            Result<T::Response>,
1458            &mut Context<Self>,
1459        ) -> Option<T::Response>
1460        + 'static,
1461        cx: &mut Context<Self>,
1462    ) -> Task<Option<T::Response>> {
1463        if !T::is_supported(&capabilities) {
1464            log::warn!(
1465                "Attempted to send a DAP request that isn't supported: {:?}",
1466                request
1467            );
1468            let error = Err(anyhow::Error::msg(
1469                "Couldn't complete request because it's not supported",
1470            ));
1471            return cx.spawn(async move |this, cx| {
1472                this.update(cx, |this, cx| process_result(this, error, cx))
1473                    .ok()
1474                    .flatten()
1475            });
1476        }
1477
1478        let request = mode.request_dap(request);
1479        cx.spawn(async move |this, cx| {
1480            let result = request.await;
1481            this.update(cx, |this, cx| process_result(this, result, cx))
1482                .ok()
1483                .flatten()
1484        })
1485    }
1486
1487    fn request<T: DapCommand + PartialEq + Eq + Hash>(
1488        &self,
1489        request: T,
1490        process_result: impl FnOnce(
1491            &mut Self,
1492            Result<T::Response>,
1493            &mut Context<Self>,
1494        ) -> Option<T::Response>
1495        + 'static,
1496        cx: &mut Context<Self>,
1497    ) -> Task<Option<T::Response>> {
1498        Self::request_inner(&self.capabilities, &self.mode, request, process_result, cx)
1499    }
1500
1501    fn invalidate_command_type<Command: DapCommand>(&mut self) {
1502        self.requests.remove(&std::any::TypeId::of::<Command>());
1503    }
1504
1505    fn invalidate_generic(&mut self) {
1506        self.invalidate_command_type::<ModulesCommand>();
1507        self.invalidate_command_type::<LoadedSourcesCommand>();
1508        self.invalidate_command_type::<ThreadsCommand>();
1509    }
1510
1511    fn invalidate_state(&mut self, key: &RequestSlot) {
1512        self.requests
1513            .entry((&*key.0 as &dyn Any).type_id())
1514            .and_modify(|request_map| {
1515                request_map.remove(&key);
1516            });
1517    }
1518
1519    fn push_output(&mut self, event: OutputEvent, cx: &mut Context<Self>) {
1520        self.output.push_back(event);
1521        self.output_token.0 += 1;
1522        cx.emit(SessionEvent::ConsoleOutput);
1523    }
1524
1525    pub fn any_stopped_thread(&self) -> bool {
1526        self.thread_states.any_stopped_thread()
1527    }
1528
1529    pub fn thread_status(&self, thread_id: ThreadId) -> ThreadStatus {
1530        self.thread_states.thread_status(thread_id)
1531    }
1532
1533    pub fn threads(&mut self, cx: &mut Context<Self>) -> Vec<(dap::Thread, ThreadStatus)> {
1534        self.fetch(
1535            dap_command::ThreadsCommand,
1536            |this, result, cx| {
1537                let result = result.log_err()?;
1538
1539                this.threads = result
1540                    .iter()
1541                    .map(|thread| (ThreadId(thread.id), Thread::from(thread.clone())))
1542                    .collect();
1543
1544                this.invalidate_command_type::<StackTraceCommand>();
1545                cx.emit(SessionEvent::Threads);
1546                cx.notify();
1547
1548                Some(result)
1549            },
1550            cx,
1551        );
1552
1553        self.threads
1554            .values()
1555            .map(|thread| {
1556                (
1557                    thread.dap.clone(),
1558                    self.thread_states.thread_status(ThreadId(thread.dap.id)),
1559                )
1560            })
1561            .collect()
1562    }
1563
1564    pub fn modules(&mut self, cx: &mut Context<Self>) -> &[Module] {
1565        self.fetch(
1566            dap_command::ModulesCommand,
1567            |this, result, cx| {
1568                let result = result.log_err()?;
1569
1570                this.modules = result.iter().cloned().collect();
1571                cx.emit(SessionEvent::Modules);
1572                cx.notify();
1573
1574                Some(result)
1575            },
1576            cx,
1577        );
1578
1579        &self.modules
1580    }
1581
1582    pub fn ignore_breakpoints(&self) -> bool {
1583        self.ignore_breakpoints
1584    }
1585
1586    pub fn toggle_ignore_breakpoints(
1587        &mut self,
1588        cx: &mut App,
1589    ) -> Task<HashMap<Arc<Path>, anyhow::Error>> {
1590        self.set_ignore_breakpoints(!self.ignore_breakpoints, cx)
1591    }
1592
1593    pub(crate) fn set_ignore_breakpoints(
1594        &mut self,
1595        ignore: bool,
1596        cx: &mut App,
1597    ) -> Task<HashMap<Arc<Path>, anyhow::Error>> {
1598        if self.ignore_breakpoints == ignore {
1599            return Task::ready(HashMap::default());
1600        }
1601
1602        self.ignore_breakpoints = ignore;
1603
1604        if let Some(local) = self.as_running() {
1605            local.send_source_breakpoints(ignore, &self.breakpoint_store, cx)
1606        } else {
1607            // todo(debugger): We need to propagate this change to downstream sessions and send a message to upstream sessions
1608            unimplemented!()
1609        }
1610    }
1611
1612    pub fn exception_breakpoints(
1613        &self,
1614    ) -> impl Iterator<Item = &(ExceptionBreakpointsFilter, IsEnabled)> {
1615        self.exception_breakpoints.values()
1616    }
1617
1618    pub fn toggle_exception_breakpoint(&mut self, id: &str, cx: &App) {
1619        if let Some((_, is_enabled)) = self.exception_breakpoints.get_mut(id) {
1620            *is_enabled = !*is_enabled;
1621            self.send_exception_breakpoints(cx);
1622        }
1623    }
1624
1625    fn send_exception_breakpoints(&mut self, cx: &App) {
1626        if let Some(local) = self.as_running() {
1627            let exception_filters = self
1628                .exception_breakpoints
1629                .values()
1630                .filter_map(|(filter, is_enabled)| is_enabled.then(|| filter.clone()))
1631                .collect();
1632
1633            let supports_exception_filters = self
1634                .capabilities
1635                .supports_exception_filter_options
1636                .unwrap_or_default();
1637            local
1638                .send_exception_breakpoints(exception_filters, supports_exception_filters)
1639                .detach_and_log_err(cx);
1640        } else {
1641            debug_assert!(false, "Not implemented");
1642        }
1643    }
1644
1645    pub fn breakpoints_enabled(&self) -> bool {
1646        self.ignore_breakpoints
1647    }
1648
1649    pub fn loaded_sources(&mut self, cx: &mut Context<Self>) -> &[Source] {
1650        self.fetch(
1651            dap_command::LoadedSourcesCommand,
1652            |this, result, cx| {
1653                let result = result.log_err()?;
1654                this.loaded_sources = result.iter().cloned().collect();
1655                cx.emit(SessionEvent::LoadedSources);
1656                cx.notify();
1657                Some(result)
1658            },
1659            cx,
1660        );
1661
1662        &self.loaded_sources
1663    }
1664
1665    fn fallback_to_manual_restart(
1666        &mut self,
1667        res: Result<()>,
1668        cx: &mut Context<Self>,
1669    ) -> Option<()> {
1670        if res.log_err().is_none() {
1671            cx.emit(SessionStateEvent::Restart);
1672            return None;
1673        }
1674        Some(())
1675    }
1676
1677    fn empty_response(&mut self, res: Result<()>, _cx: &mut Context<Self>) -> Option<()> {
1678        res.log_err()?;
1679        Some(())
1680    }
1681
1682    fn on_step_response<T: DapCommand + PartialEq + Eq + Hash>(
1683        thread_id: ThreadId,
1684    ) -> impl FnOnce(&mut Self, Result<T::Response>, &mut Context<Self>) -> Option<T::Response> + 'static
1685    {
1686        move |this, response, cx| match response.log_err() {
1687            Some(response) => {
1688                this.breakpoint_store.update(cx, |store, cx| {
1689                    store.remove_active_position(Some(this.session_id()), cx)
1690                });
1691                Some(response)
1692            }
1693            None => {
1694                this.thread_states.stop_thread(thread_id);
1695                cx.notify();
1696                None
1697            }
1698        }
1699    }
1700
1701    fn clear_active_debug_line_response(
1702        &mut self,
1703        response: Result<()>,
1704        cx: &mut Context<Session>,
1705    ) -> Option<()> {
1706        response.log_err()?;
1707        self.clear_active_debug_line(cx);
1708        Some(())
1709    }
1710
1711    fn clear_active_debug_line(&mut self, cx: &mut Context<Session>) {
1712        self.breakpoint_store.update(cx, |store, cx| {
1713            store.remove_active_position(Some(self.id), cx)
1714        });
1715    }
1716
1717    pub fn pause_thread(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) {
1718        self.request(
1719            PauseCommand {
1720                thread_id: thread_id.0,
1721            },
1722            Self::empty_response,
1723            cx,
1724        )
1725        .detach();
1726    }
1727
1728    pub fn restart_stack_frame(&mut self, stack_frame_id: u64, cx: &mut Context<Self>) {
1729        self.request(
1730            RestartStackFrameCommand { stack_frame_id },
1731            Self::empty_response,
1732            cx,
1733        )
1734        .detach();
1735    }
1736
1737    pub fn restart(&mut self, args: Option<Value>, cx: &mut Context<Self>) {
1738        if self.capabilities.supports_restart_request.unwrap_or(false) && !self.is_terminated() {
1739            self.request(
1740                RestartCommand {
1741                    raw: args.unwrap_or(Value::Null),
1742                },
1743                Self::fallback_to_manual_restart,
1744                cx,
1745            )
1746            .detach();
1747        } else {
1748            cx.emit(SessionStateEvent::Restart);
1749        }
1750    }
1751
1752    fn on_app_quit(&mut self, cx: &mut Context<Self>) -> Task<()> {
1753        let debug_adapter = self.adapter_client();
1754
1755        cx.background_spawn(async move {
1756            if let Some(client) = debug_adapter {
1757                client.shutdown().await.log_err();
1758            }
1759        })
1760    }
1761
1762    pub fn shutdown(&mut self, cx: &mut Context<Self>) -> Task<()> {
1763        self.is_session_terminated = true;
1764        self.thread_states.exit_all_threads();
1765        cx.notify();
1766
1767        let task = if self
1768            .capabilities
1769            .supports_terminate_request
1770            .unwrap_or_default()
1771        {
1772            self.request(
1773                TerminateCommand {
1774                    restart: Some(false),
1775                },
1776                Self::clear_active_debug_line_response,
1777                cx,
1778            )
1779        } else {
1780            self.request(
1781                DisconnectCommand {
1782                    restart: Some(false),
1783                    terminate_debuggee: Some(true),
1784                    suspend_debuggee: Some(false),
1785                },
1786                Self::clear_active_debug_line_response,
1787                cx,
1788            )
1789        };
1790
1791        cx.emit(SessionStateEvent::Shutdown);
1792
1793        let debug_client = self.adapter_client();
1794
1795        cx.background_spawn(async move {
1796            let _ = task.await;
1797
1798            if let Some(client) = debug_client {
1799                client.shutdown().await.log_err();
1800            }
1801        })
1802    }
1803
1804    pub fn completions(
1805        &mut self,
1806        query: CompletionsQuery,
1807        cx: &mut Context<Self>,
1808    ) -> Task<Result<Vec<dap::CompletionItem>>> {
1809        let task = self.request(query, |_, result, _| result.log_err(), cx);
1810
1811        cx.background_executor().spawn(async move {
1812            anyhow::Ok(
1813                task.await
1814                    .map(|response| response.targets)
1815                    .context("failed to fetch completions")?,
1816            )
1817        })
1818    }
1819
1820    pub fn continue_thread(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) {
1821        self.thread_states.continue_thread(thread_id);
1822        self.request(
1823            ContinueCommand {
1824                args: ContinueArguments {
1825                    thread_id: thread_id.0,
1826                    single_thread: Some(true),
1827                },
1828            },
1829            Self::on_step_response::<ContinueCommand>(thread_id),
1830            cx,
1831        )
1832        .detach();
1833    }
1834
1835    pub fn adapter_client(&self) -> Option<Arc<DebugAdapterClient>> {
1836        match self.mode {
1837            Mode::Running(ref local) => Some(local.client.clone()),
1838            Mode::Building => None,
1839        }
1840    }
1841
1842    pub fn step_over(
1843        &mut self,
1844        thread_id: ThreadId,
1845        granularity: SteppingGranularity,
1846        cx: &mut Context<Self>,
1847    ) {
1848        let supports_single_thread_execution_requests =
1849            self.capabilities.supports_single_thread_execution_requests;
1850        let supports_stepping_granularity = self
1851            .capabilities
1852            .supports_stepping_granularity
1853            .unwrap_or_default();
1854
1855        let command = NextCommand {
1856            inner: StepCommand {
1857                thread_id: thread_id.0,
1858                granularity: supports_stepping_granularity.then(|| granularity),
1859                single_thread: supports_single_thread_execution_requests,
1860            },
1861        };
1862
1863        self.thread_states.process_step(thread_id);
1864        self.request(
1865            command,
1866            Self::on_step_response::<NextCommand>(thread_id),
1867            cx,
1868        )
1869        .detach();
1870    }
1871
1872    pub fn step_in(
1873        &mut self,
1874        thread_id: ThreadId,
1875        granularity: SteppingGranularity,
1876        cx: &mut Context<Self>,
1877    ) {
1878        let supports_single_thread_execution_requests =
1879            self.capabilities.supports_single_thread_execution_requests;
1880        let supports_stepping_granularity = self
1881            .capabilities
1882            .supports_stepping_granularity
1883            .unwrap_or_default();
1884
1885        let command = StepInCommand {
1886            inner: StepCommand {
1887                thread_id: thread_id.0,
1888                granularity: supports_stepping_granularity.then(|| granularity),
1889                single_thread: supports_single_thread_execution_requests,
1890            },
1891        };
1892
1893        self.thread_states.process_step(thread_id);
1894        self.request(
1895            command,
1896            Self::on_step_response::<StepInCommand>(thread_id),
1897            cx,
1898        )
1899        .detach();
1900    }
1901
1902    pub fn step_out(
1903        &mut self,
1904        thread_id: ThreadId,
1905        granularity: SteppingGranularity,
1906        cx: &mut Context<Self>,
1907    ) {
1908        let supports_single_thread_execution_requests =
1909            self.capabilities.supports_single_thread_execution_requests;
1910        let supports_stepping_granularity = self
1911            .capabilities
1912            .supports_stepping_granularity
1913            .unwrap_or_default();
1914
1915        let command = StepOutCommand {
1916            inner: StepCommand {
1917                thread_id: thread_id.0,
1918                granularity: supports_stepping_granularity.then(|| granularity),
1919                single_thread: supports_single_thread_execution_requests,
1920            },
1921        };
1922
1923        self.thread_states.process_step(thread_id);
1924        self.request(
1925            command,
1926            Self::on_step_response::<StepOutCommand>(thread_id),
1927            cx,
1928        )
1929        .detach();
1930    }
1931
1932    pub fn step_back(
1933        &mut self,
1934        thread_id: ThreadId,
1935        granularity: SteppingGranularity,
1936        cx: &mut Context<Self>,
1937    ) {
1938        let supports_single_thread_execution_requests =
1939            self.capabilities.supports_single_thread_execution_requests;
1940        let supports_stepping_granularity = self
1941            .capabilities
1942            .supports_stepping_granularity
1943            .unwrap_or_default();
1944
1945        let command = StepBackCommand {
1946            inner: StepCommand {
1947                thread_id: thread_id.0,
1948                granularity: supports_stepping_granularity.then(|| granularity),
1949                single_thread: supports_single_thread_execution_requests,
1950            },
1951        };
1952
1953        self.thread_states.process_step(thread_id);
1954
1955        self.request(
1956            command,
1957            Self::on_step_response::<StepBackCommand>(thread_id),
1958            cx,
1959        )
1960        .detach();
1961    }
1962
1963    pub fn stack_frames(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) -> Vec<StackFrame> {
1964        if self.thread_states.thread_status(thread_id) == ThreadStatus::Stopped
1965            && self.requests.contains_key(&ThreadsCommand.type_id())
1966            && self.threads.contains_key(&thread_id)
1967        // ^ todo(debugger): We need a better way to check that we're not querying stale data
1968        // We could still be using an old thread id and have sent a new thread's request
1969        // This isn't the biggest concern right now because it hasn't caused any issues outside of tests
1970        // But it very well could cause a minor bug in the future that is hard to track down
1971        {
1972            self.fetch(
1973                super::dap_command::StackTraceCommand {
1974                    thread_id: thread_id.0,
1975                    start_frame: None,
1976                    levels: None,
1977                },
1978                move |this, stack_frames, cx| {
1979                    let stack_frames = stack_frames.log_err()?;
1980
1981                    let entry = this.threads.entry(thread_id).and_modify(|thread| {
1982                        thread.stack_frames =
1983                            stack_frames.iter().cloned().map(StackFrame::from).collect();
1984                    });
1985                    debug_assert!(
1986                        matches!(entry, indexmap::map::Entry::Occupied(_)),
1987                        "Sent request for thread_id that doesn't exist"
1988                    );
1989
1990                    this.stack_frames.extend(
1991                        stack_frames
1992                            .iter()
1993                            .filter(|frame| {
1994                                // Workaround for JavaScript debug adapter sending out "fake" stack frames for delineating await points. This is fine,
1995                                // except that they always use an id of 0 for it, which collides with other (valid) stack frames.
1996                                !(frame.id == 0
1997                                    && frame.line == 0
1998                                    && frame.column == 0
1999                                    && frame.presentation_hint
2000                                        == Some(StackFramePresentationHint::Label))
2001                            })
2002                            .cloned()
2003                            .map(|frame| (frame.id, StackFrame::from(frame))),
2004                    );
2005
2006                    this.invalidate_command_type::<ScopesCommand>();
2007                    this.invalidate_command_type::<VariablesCommand>();
2008
2009                    cx.emit(SessionEvent::StackTrace);
2010                    cx.notify();
2011                    Some(stack_frames)
2012                },
2013                cx,
2014            );
2015        }
2016
2017        self.threads
2018            .get(&thread_id)
2019            .map(|thread| thread.stack_frames.clone())
2020            .unwrap_or_default()
2021    }
2022
2023    pub fn scopes(&mut self, stack_frame_id: u64, cx: &mut Context<Self>) -> &[dap::Scope] {
2024        if self.requests.contains_key(&TypeId::of::<ThreadsCommand>())
2025            && self
2026                .requests
2027                .contains_key(&TypeId::of::<StackTraceCommand>())
2028        {
2029            self.fetch(
2030                ScopesCommand { stack_frame_id },
2031                move |this, scopes, cx| {
2032                    let scopes = scopes.log_err()?;
2033
2034                    for scope in scopes .iter(){
2035                        this.variables(scope.variables_reference, cx);
2036                    }
2037
2038                    let entry = this
2039                        .stack_frames
2040                        .entry(stack_frame_id)
2041                        .and_modify(|stack_frame| {
2042                            stack_frame.scopes = scopes.clone();
2043                        });
2044
2045                    cx.emit(SessionEvent::Variables);
2046
2047                    debug_assert!(
2048                        matches!(entry, indexmap::map::Entry::Occupied(_)),
2049                        "Sent scopes request for stack_frame_id that doesn't exist or hasn't been fetched"
2050                    );
2051
2052                    Some(scopes)
2053                },
2054                cx,
2055            );
2056        }
2057
2058        self.stack_frames
2059            .get(&stack_frame_id)
2060            .map(|frame| frame.scopes.as_slice())
2061            .unwrap_or_default()
2062    }
2063
2064    pub fn variables_by_stack_frame_id(&self, stack_frame_id: StackFrameId) -> Vec<dap::Variable> {
2065        let Some(stack_frame) = self.stack_frames.get(&stack_frame_id) else {
2066            return Vec::new();
2067        };
2068
2069        stack_frame
2070            .scopes
2071            .iter()
2072            .filter_map(|scope| self.variables.get(&scope.variables_reference))
2073            .flatten()
2074            .cloned()
2075            .collect()
2076    }
2077
2078    pub fn variables(
2079        &mut self,
2080        variables_reference: VariableReference,
2081        cx: &mut Context<Self>,
2082    ) -> Vec<dap::Variable> {
2083        let command = VariablesCommand {
2084            variables_reference,
2085            filter: None,
2086            start: None,
2087            count: None,
2088            format: None,
2089        };
2090
2091        self.fetch(
2092            command,
2093            move |this, variables, cx| {
2094                let variables = variables.log_err()?;
2095                this.variables
2096                    .insert(variables_reference, variables.clone());
2097
2098                cx.emit(SessionEvent::Variables);
2099                cx.emit(SessionEvent::InvalidateInlineValue);
2100                Some(variables)
2101            },
2102            cx,
2103        );
2104
2105        self.variables
2106            .get(&variables_reference)
2107            .cloned()
2108            .unwrap_or_default()
2109    }
2110
2111    pub fn set_variable_value(
2112        &mut self,
2113        variables_reference: u64,
2114        name: String,
2115        value: String,
2116        cx: &mut Context<Self>,
2117    ) {
2118        if self.capabilities.supports_set_variable.unwrap_or_default() {
2119            self.request(
2120                SetVariableValueCommand {
2121                    name,
2122                    value,
2123                    variables_reference,
2124                },
2125                move |this, response, cx| {
2126                    let response = response.log_err()?;
2127                    this.invalidate_command_type::<VariablesCommand>();
2128                    cx.notify();
2129                    Some(response)
2130                },
2131                cx,
2132            )
2133            .detach()
2134        }
2135    }
2136
2137    pub fn evaluate(
2138        &mut self,
2139        expression: String,
2140        context: Option<EvaluateArgumentsContext>,
2141        frame_id: Option<u64>,
2142        source: Option<Source>,
2143        cx: &mut Context<Self>,
2144    ) -> Task<()> {
2145        let event = dap::OutputEvent {
2146            category: None,
2147            output: format!("> {expression}"),
2148            group: None,
2149            variables_reference: None,
2150            source: None,
2151            line: None,
2152            column: None,
2153            data: None,
2154            location_reference: None,
2155        };
2156        self.push_output(event, cx);
2157        let request = self.mode.request_dap(EvaluateCommand {
2158            expression,
2159            context,
2160            frame_id,
2161            source,
2162        });
2163        cx.spawn(async move |this, cx| {
2164            let response = request.await;
2165            this.update(cx, |this, cx| {
2166                match response {
2167                    Ok(response) => {
2168                        let event = dap::OutputEvent {
2169                            category: None,
2170                            output: format!("< {}", &response.result),
2171                            group: None,
2172                            variables_reference: Some(response.variables_reference),
2173                            source: None,
2174                            line: None,
2175                            column: None,
2176                            data: None,
2177                            location_reference: None,
2178                        };
2179                        this.push_output(event, cx);
2180                    }
2181                    Err(e) => {
2182                        let event = dap::OutputEvent {
2183                            category: None,
2184                            output: format!("{}", e),
2185                            group: None,
2186                            variables_reference: None,
2187                            source: None,
2188                            line: None,
2189                            column: None,
2190                            data: None,
2191                            location_reference: None,
2192                        };
2193                        this.push_output(event, cx);
2194                    }
2195                };
2196                this.invalidate_command_type::<ScopesCommand>();
2197                cx.notify();
2198            })
2199            .ok();
2200        })
2201    }
2202
2203    pub fn location(
2204        &mut self,
2205        reference: u64,
2206        cx: &mut Context<Self>,
2207    ) -> Option<dap::LocationsResponse> {
2208        self.fetch(
2209            LocationsCommand { reference },
2210            move |this, response, _| {
2211                let response = response.log_err()?;
2212                this.locations.insert(reference, response.clone());
2213                Some(response)
2214            },
2215            cx,
2216        );
2217        self.locations.get(&reference).cloned()
2218    }
2219
2220    pub fn disconnect_client(&mut self, cx: &mut Context<Self>) {
2221        let command = DisconnectCommand {
2222            restart: Some(false),
2223            terminate_debuggee: Some(true),
2224            suspend_debuggee: Some(false),
2225        };
2226
2227        self.request(command, Self::empty_response, cx).detach()
2228    }
2229
2230    pub fn terminate_threads(&mut self, thread_ids: Option<Vec<ThreadId>>, cx: &mut Context<Self>) {
2231        if self
2232            .capabilities
2233            .supports_terminate_threads_request
2234            .unwrap_or_default()
2235        {
2236            self.request(
2237                TerminateThreadsCommand {
2238                    thread_ids: thread_ids.map(|ids| ids.into_iter().map(|id| id.0).collect()),
2239                },
2240                Self::clear_active_debug_line_response,
2241                cx,
2242            )
2243            .detach();
2244        } else {
2245            self.shutdown(cx).detach();
2246        }
2247    }
2248
2249    pub fn thread_state(&self, thread_id: ThreadId) -> Option<ThreadStatus> {
2250        self.thread_states.thread_state(thread_id)
2251    }
2252}