diff --git a/Cargo.lock b/Cargo.lock index a8f0096a7a1219ee30b287c61efd9f77f4b9d223..0bbde0bdfddb0b11b715bce230cb82cb4c74cb0f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -4583,6 +4583,7 @@ dependencies = [ "db", "debugger_tools", "editor", + "feature_flags", "file_icons", "futures 0.3.31", "fuzzy", diff --git a/crates/debugger_ui/Cargo.toml b/crates/debugger_ui/Cargo.toml index 25d23b96b897001faec39498c5b08ef08b09a3a1..fb79b1b0790b28d7204774720bf9c413cfed64e6 100644 --- a/crates/debugger_ui/Cargo.toml +++ b/crates/debugger_ui/Cargo.toml @@ -37,6 +37,7 @@ dap_adapters = { workspace = true, optional = true } db.workspace = true debugger_tools.workspace = true editor.workspace = true +feature_flags.workspace = true file_icons.workspace = true futures.workspace = true fuzzy.workspace = true diff --git a/crates/debugger_ui/src/debugger_panel.rs b/crates/debugger_ui/src/debugger_panel.rs index ffdd4a22e3d092eb5d3d6626dcfe8b167ae03936..fe81ac641196dbbc5ceecaede0785ca72336c261 100644 --- a/crates/debugger_ui/src/debugger_panel.rs +++ b/crates/debugger_ui/src/debugger_panel.rs @@ -15,6 +15,7 @@ use dap::adapters::DebugAdapterName; use dap::{DapRegistry, StartDebuggingRequestArguments}; use dap::{client::SessionId, debugger_settings::DebuggerSettings}; use editor::{Editor, MultiBufferOffset, ToPoint}; +use feature_flags::{FeatureFlag, FeatureFlagAppExt as _}; use gpui::{ Action, App, AsyncWindowContext, ClipboardItem, Context, DismissEvent, Entity, EntityId, EventEmitter, FocusHandle, Focusable, MouseButton, MouseDownEvent, Point, Subscription, Task, @@ -42,6 +43,12 @@ use workspace::{ }; use zed_actions::ToggleFocus; +pub struct DebuggerHistoryFeatureFlag; + +impl FeatureFlag for DebuggerHistoryFeatureFlag { + const NAME: &'static str = "debugger-history"; +} + const DEBUG_PANEL_KEY: &str = "DebugPanel"; pub struct DebugPanel { @@ -284,7 +291,7 @@ impl DebugPanel { } }); - session.update(cx, |session, _| match &mut session.mode { + session.update(cx, |session, _| match &mut session.state { SessionState::Booting(state_task) => { *state_task = Some(boot_task); } @@ -805,6 +812,34 @@ impl DebugPanel { } }), ) + .when(cx.has_flag::(), |this| { + this.child( + IconButton::new( + "debug-back-in-history", + IconName::HistoryRerun, + ) + .icon_size(IconSize::Small) + .on_click( + window.listener_for( + running_state, + |this, _, _window, cx| { + this.session().update(cx, |session, cx| { + let ix = session + .active_history() + .unwrap_or_else(|| { + session.history().len() + }); + + session.go_back_to_history( + Some(ix.saturating_sub(1)), + cx, + ); + }) + }, + ), + ), + ) + }) .child(Divider::vertical()) .child( IconButton::new("debug-restart", IconName::RotateCcw) diff --git a/crates/debugger_ui/src/session/running.rs b/crates/debugger_ui/src/session/running.rs index b82f839edee82f884c1419d44a2344c39c8abd0d..bc99d6ac8e42b0a706df4a09177ae2103d5939e2 100644 --- a/crates/debugger_ui/src/session/running.rs +++ b/crates/debugger_ui/src/session/running.rs @@ -1743,7 +1743,7 @@ impl RunningState { let is_building = self.session.update(cx, |session, cx| { session.shutdown(cx).detach(); - matches!(session.mode, session::SessionState::Booting(_)) + matches!(session.state, session::SessionState::Booting(_)) }); if is_building { diff --git a/crates/project/src/debugger/dap_store.rs b/crates/project/src/debugger/dap_store.rs index a82286441d625561009f4f9259f5c06fe424ff10..4a588e7c436f5f29fffd953b8fce988daa4655d8 100644 --- a/crates/project/src/debugger/dap_store.rs +++ b/crates/project/src/debugger/dap_store.rs @@ -692,7 +692,7 @@ impl DapStore { } VariableLookupKind::Expression => { let Ok(eval_task) = session.read_with(cx, |session, _| { - session.mode.request_dap(EvaluateCommand { + session.state.request_dap(EvaluateCommand { expression: inline_value_location.variable_name.clone(), frame_id: Some(stack_frame_id), source: None, diff --git a/crates/project/src/debugger/session.rs b/crates/project/src/debugger/session.rs index 47fe98827cbc163682ef6f002eff4008967d4ced..a63e9066c9a30233ee1edb15aac13da145cb76b2 100644 --- a/crates/project/src/debugger/session.rs +++ b/crates/project/src/debugger/session.rs @@ -1,7 +1,3 @@ -use crate::debugger::breakpoint_store::BreakpointSessionState; -use crate::debugger::dap_command::{DataBreakpointContext, ReadMemory}; -use crate::debugger::memory::{self, Memory, MemoryIterator, MemoryPageBuilder, PageAddress}; - use super::breakpoint_store::{ BreakpointStore, BreakpointStoreEvent, BreakpointUpdatedReason, SourceBreakpoint, }; @@ -14,6 +10,9 @@ use super::dap_command::{ TerminateCommand, TerminateThreadsCommand, ThreadsCommand, VariablesCommand, }; use super::dap_store::DapStore; +use crate::debugger::breakpoint_store::BreakpointSessionState; +use crate::debugger::dap_command::{DataBreakpointContext, ReadMemory}; +use crate::debugger::memory::{self, Memory, MemoryIterator, MemoryPageBuilder, PageAddress}; use anyhow::{Context as _, Result, anyhow, bail}; use base64::Engine; use collections::{HashMap, HashSet, IndexMap}; @@ -42,15 +41,13 @@ use gpui::{ Task, WeakEntity, }; use http_client::HttpClient; - use node_runtime::NodeRuntime; use remote::RemoteClient; -use rpc::ErrorExt; use serde::{Deserialize, Serialize}; use serde_json::Value; use smol::net::{TcpListener, TcpStream}; use std::any::TypeId; -use std::collections::BTreeMap; +use std::collections::{BTreeMap, VecDeque}; use std::net::Ipv4Addr; use std::ops::RangeInclusive; use std::path::PathBuf; @@ -71,6 +68,9 @@ use util::command::new_smol_command; use util::{ResultExt, debug_panic, maybe}; use worktree::Worktree; +const MAX_TRACKED_OUTPUT_EVENTS: usize = 5000; +const DEBUG_HISTORY_LIMIT: usize = 10; + #[derive(Debug, Copy, Clone, Hash, PartialEq, PartialOrd, Ord, Eq)] #[repr(transparent)] pub struct ThreadId(pub i64); @@ -118,11 +118,11 @@ impl ThreadStatus { } } -#[derive(Debug)] +#[derive(Debug, Clone)] pub struct Thread { dap: dap::Thread, stack_frames: Vec, - stack_frames_error: Option, + stack_frames_error: Option, _has_stopped: bool, } @@ -672,7 +672,18 @@ impl ThreadStates { .any(|status| *status == ThreadStatus::Stopped) } } -const MAX_TRACKED_OUTPUT_EVENTS: usize = 5000; + +// TODO(debugger): Wrap dap types with reference counting so the UI doesn't have to clone them on refresh +#[derive(Default)] +pub struct SessionSnapshot { + threads: IndexMap, + thread_states: ThreadStates, + variables: HashMap>, + stack_frames: IndexMap, + locations: HashMap, + modules: Vec, + loaded_sources: Vec, +} type IsEnabled = bool; @@ -680,23 +691,19 @@ type IsEnabled = bool; pub struct OutputToken(pub usize); /// Represents a current state of a single debug adapter and provides ways to mutate it. pub struct Session { - pub mode: SessionState, + pub state: SessionState, + active_snapshot: SessionSnapshot, + snapshots: VecDeque, + selected_snapshot_index: Option, id: SessionId, label: Option, adapter: DebugAdapterName, pub(super) capabilities: Capabilities, child_session_ids: HashSet, parent_session: Option>, - modules: Vec, - loaded_sources: Vec, output_token: OutputToken, output: Box>, - threads: IndexMap, - thread_states: ThreadStates, watchers: HashMap, - variables: HashMap>, - stack_frames: IndexMap, - locations: HashMap, is_session_terminated: bool, requests: HashMap>>>>, pub(crate) breakpoint_store: Entity, @@ -858,24 +865,20 @@ impl Session { .detach(); Self { - mode: SessionState::Booting(None), + state: SessionState::Booting(None), + snapshots: VecDeque::with_capacity(DEBUG_HISTORY_LIMIT), + selected_snapshot_index: None, + active_snapshot: Default::default(), id: session_id, child_session_ids: HashSet::default(), parent_session, capabilities: Capabilities::default(), watchers: HashMap::default(), - variables: Default::default(), - stack_frames: Default::default(), - thread_states: ThreadStates::default(), output_token: OutputToken(0), output: circular_buffer::CircularBuffer::boxed(), requests: HashMap::default(), - modules: Vec::default(), - loaded_sources: Vec::default(), - threads: IndexMap::default(), background_tasks: Vec::default(), restart_task: None, - locations: Default::default(), is_session_terminated: false, ignore_breakpoints: false, breakpoint_store, @@ -899,7 +902,7 @@ impl Session { } pub fn worktree(&self) -> Option> { - match &self.mode { + match &self.state { SessionState::Booting(_) => None, SessionState::Running(local_mode) => local_mode.worktree.upgrade(), } @@ -960,7 +963,7 @@ impl Session { ) .await?; this.update(cx, |this, cx| { - match &mut this.mode { + match &mut this.state { SessionState::Booting(task) if task.is_some() => { task.take().unwrap().detach_and_log_err(cx); } @@ -969,7 +972,7 @@ impl Session { debug_panic!("Attempting to boot a session that is already running"); } }; - this.mode = SessionState::Running(mode); + this.state = SessionState::Running(mode); cx.emit(SessionStateEvent::Running); })?; @@ -1061,7 +1064,7 @@ impl Session { } pub fn binary(&self) -> Option<&DebugAdapterBinary> { - match &self.mode { + match &self.state { SessionState::Booting(_) => None, SessionState::Running(running_mode) => Some(&running_mode.binary), } @@ -1107,25 +1110,25 @@ impl Session { } pub fn is_started(&self) -> bool { - match &self.mode { + match &self.state { SessionState::Booting(_) => false, SessionState::Running(running) => running.is_started, } } pub fn is_building(&self) -> bool { - matches!(self.mode, SessionState::Booting(_)) + matches!(self.state, SessionState::Booting(_)) } pub fn as_running_mut(&mut self) -> Option<&mut RunningMode> { - match &mut self.mode { + match &mut self.state { SessionState::Running(local_mode) => Some(local_mode), SessionState::Booting(_) => None, } } pub fn as_running(&self) -> Option<&RunningMode> { - match &self.mode { + match &self.state { SessionState::Running(local_mode) => Some(local_mode), SessionState::Booting(_) => None, } @@ -1269,7 +1272,7 @@ impl Session { let adapter_id = self.adapter().to_string(); let request = Initialize { adapter_id }; - let SessionState::Running(running) = &self.mode else { + let SessionState::Running(running) = &self.state else { return Task::ready(Err(anyhow!( "Cannot send initialize request, task still building" ))); @@ -1317,7 +1320,7 @@ impl Session { dap_store: WeakEntity, cx: &mut Context, ) -> Task> { - match &self.mode { + match &self.state { SessionState::Running(local_mode) => { local_mode.initialize_sequence(&self.capabilities, initialize_rx, dap_store, cx) } @@ -1333,10 +1336,12 @@ impl Session { active_thread_id: ThreadId, cx: &mut Context, ) { - match &mut self.mode { + match &mut self.state { SessionState::Running(local_mode) => { if !matches!( - self.thread_states.thread_state(active_thread_id), + self.active_snapshot + .thread_states + .thread_state(active_thread_id), Some(ThreadStatus::Stopped) ) { return; @@ -1411,8 +1416,51 @@ impl Session { }) } + fn session_state(&self) -> &SessionSnapshot { + self.selected_snapshot_index + .and_then(|ix| self.snapshots.get(ix)) + .unwrap_or_else(|| &self.active_snapshot) + } + + fn push_to_history(&mut self) { + if !self.has_ever_stopped() { + return; + } + + while self.snapshots.len() >= DEBUG_HISTORY_LIMIT { + self.snapshots.pop_front(); + } + + self.snapshots + .push_back(std::mem::take(&mut self.active_snapshot)); + } + + pub fn history(&self) -> &VecDeque { + &self.snapshots + } + + pub fn go_back_to_history(&mut self, ix: Option, cx: &mut Context<'_, Session>) { + if self.selected_snapshot_index == ix { + return; + } + + self.selected_snapshot_index = ix; + + if ix.is_some() { + cx.emit(SessionEvent::Stopped(None)); + } + + cx.notify(); + } + + pub fn active_history(&self) -> Option { + self.selected_snapshot_index + } + fn handle_stopped_event(&mut self, event: StoppedEvent, cx: &mut Context) { - self.mode.stopped(); + self.push_to_history(); + + self.state.stopped(); // todo(debugger): Find a clean way to get around the clone let breakpoint_store = self.breakpoint_store.clone(); if let Some((local, path)) = self.as_running_mut().and_then(|local| { @@ -1431,14 +1479,16 @@ impl Session { }; if event.all_threads_stopped.unwrap_or_default() || event.thread_id.is_none() { - self.thread_states.stop_all_threads(); + self.active_snapshot.thread_states.stop_all_threads(); self.invalidate_command_type::(); } // Event if we stopped all threads we still need to insert the thread_id // to our own data if let Some(thread_id) = event.thread_id { - self.thread_states.stop_thread(ThreadId(thread_id)); + self.active_snapshot + .thread_states + .stop_thread(ThreadId(thread_id)); self.invalidate_state( &StackTraceCommand { @@ -1451,8 +1501,8 @@ impl Session { } self.invalidate_generic(); - self.threads.clear(); - self.variables.clear(); + self.active_snapshot.threads.clear(); + self.active_snapshot.variables.clear(); cx.emit(SessionEvent::Stopped( event .thread_id @@ -1474,12 +1524,13 @@ impl Session { Events::Stopped(event) => self.handle_stopped_event(event, cx), Events::Continued(event) => { if event.all_threads_continued.unwrap_or_default() { - self.thread_states.continue_all_threads(); + self.active_snapshot.thread_states.continue_all_threads(); self.breakpoint_store.update(cx, |store, cx| { store.remove_active_position(Some(self.session_id()), cx) }); } else { - self.thread_states + self.active_snapshot + .thread_states .continue_thread(ThreadId(event.thread_id)); } // todo(debugger): We should be able to get away with only invalidating generic if all threads were continued @@ -1496,10 +1547,12 @@ impl Session { match event.reason { dap::ThreadEventReason::Started => { - self.thread_states.continue_thread(thread_id); + self.active_snapshot + .thread_states + .continue_thread(thread_id); } dap::ThreadEventReason::Exited => { - self.thread_states.exit_thread(thread_id); + self.active_snapshot.thread_states.exit_thread(thread_id); } reason => { log::error!("Unhandled thread event reason {:?}", reason); @@ -1526,10 +1579,11 @@ impl Session { Events::Module(event) => { match event.reason { dap::ModuleEventReason::New => { - self.modules.push(event.module); + self.active_snapshot.modules.push(event.module); } dap::ModuleEventReason::Changed => { if let Some(module) = self + .active_snapshot .modules .iter_mut() .find(|other| event.module.id == other.id) @@ -1538,7 +1592,9 @@ impl Session { } } dap::ModuleEventReason::Removed => { - self.modules.retain(|other| event.module.id != other.id); + self.active_snapshot + .modules + .retain(|other| event.module.id != other.id); } } @@ -1612,9 +1668,16 @@ impl Session { ); } - if !self.thread_states.any_stopped_thread() + if self.selected_snapshot_index.is_some() { + return; + } + + if self.is_session_terminated { + return; + } + + if !self.active_snapshot.thread_states.any_stopped_thread() && request.type_id() != TypeId::of::() - || self.is_session_terminated { return; } @@ -1629,7 +1692,7 @@ impl Session { let task = Self::request_inner::>( &self.capabilities, - &self.mode, + &self.state, command, |this, result, cx| { process_result(this, result, cx); @@ -1697,7 +1760,7 @@ impl Session { + 'static, cx: &mut Context, ) -> Task> { - Self::request_inner(&self.capabilities, &self.mode, request, process_result, cx) + Self::request_inner(&self.capabilities, &self.state, request, process_result, cx) } fn invalidate_command_type(&mut self) { @@ -1730,11 +1793,11 @@ impl Session { } pub fn any_stopped_thread(&self) -> bool { - self.thread_states.any_stopped_thread() + self.active_snapshot.thread_states.any_stopped_thread() } pub fn thread_status(&self, thread_id: ThreadId) -> ThreadStatus { - self.thread_states.thread_status(thread_id) + self.active_snapshot.thread_states.thread_status(thread_id) } pub fn threads(&mut self, cx: &mut Context) -> Vec<(dap::Thread, ThreadStatus)> { @@ -1745,7 +1808,7 @@ impl Session { return; }; - this.threads = result + this.active_snapshot.threads = result .into_iter() .map(|thread| (ThreadId(thread.id), Thread::from(thread))) .collect(); @@ -1757,12 +1820,14 @@ impl Session { cx, ); - self.threads + let state = self.session_state(); + state + .threads .values() .map(|thread| { ( thread.dap.clone(), - self.thread_states.thread_status(ThreadId(thread.dap.id)), + state.thread_states.thread_status(ThreadId(thread.dap.id)), ) }) .collect() @@ -1776,14 +1841,14 @@ impl Session { return; }; - this.modules = result; + this.active_snapshot.modules = result; cx.emit(SessionEvent::Modules); cx.notify(); }, cx, ); - &self.modules + &self.session_state().modules } // CodeLLDB returns the size of a pointed-to-memory, which we can use to make the experience of go-to-memory better. @@ -2034,14 +2099,13 @@ impl Session { let Some(result) = result.log_err() else { return; }; - this.loaded_sources = result; + this.active_snapshot.loaded_sources = result; cx.emit(SessionEvent::LoadedSources); cx.notify(); }, cx, ); - - &self.loaded_sources + &self.session_state().loaded_sources } fn fallback_to_manual_restart( @@ -2073,7 +2137,7 @@ impl Session { Some(response) } None => { - this.thread_states.stop_thread(thread_id); + this.active_snapshot.thread_states.stop_thread(thread_id); cx.notify(); None } @@ -2149,10 +2213,10 @@ impl Session { } self.is_session_terminated = true; - self.thread_states.exit_all_threads(); + self.active_snapshot.thread_states.exit_all_threads(); cx.notify(); - let task = match &mut self.mode { + let task = match &mut self.state { SessionState::Running(_) => { if self .capabilities @@ -2213,9 +2277,13 @@ impl Session { } pub fn continue_thread(&mut self, thread_id: ThreadId, cx: &mut Context) { + self.go_back_to_history(None, cx); + let supports_single_thread_execution_requests = self.capabilities.supports_single_thread_execution_requests; - self.thread_states.continue_thread(thread_id); + self.active_snapshot + .thread_states + .continue_thread(thread_id); self.request( ContinueCommand { args: ContinueArguments { @@ -2230,21 +2298,24 @@ impl Session { } pub fn adapter_client(&self) -> Option> { - match self.mode { + match self.state { SessionState::Running(ref local) => Some(local.client.clone()), SessionState::Booting(_) => None, } } pub fn has_ever_stopped(&self) -> bool { - self.mode.has_ever_stopped() + self.state.has_ever_stopped() } + pub fn step_over( &mut self, thread_id: ThreadId, granularity: SteppingGranularity, cx: &mut Context, ) { + self.go_back_to_history(None, cx); + let supports_single_thread_execution_requests = self.capabilities.supports_single_thread_execution_requests; let supports_stepping_granularity = self @@ -2260,7 +2331,7 @@ impl Session { }, }; - self.thread_states.process_step(thread_id); + self.active_snapshot.thread_states.process_step(thread_id); self.request( command, Self::on_step_response::(thread_id), @@ -2275,6 +2346,8 @@ impl Session { granularity: SteppingGranularity, cx: &mut Context, ) { + self.go_back_to_history(None, cx); + let supports_single_thread_execution_requests = self.capabilities.supports_single_thread_execution_requests; let supports_stepping_granularity = self @@ -2290,7 +2363,7 @@ impl Session { }, }; - self.thread_states.process_step(thread_id); + self.active_snapshot.thread_states.process_step(thread_id); self.request( command, Self::on_step_response::(thread_id), @@ -2305,6 +2378,8 @@ impl Session { granularity: SteppingGranularity, cx: &mut Context, ) { + self.go_back_to_history(None, cx); + let supports_single_thread_execution_requests = self.capabilities.supports_single_thread_execution_requests; let supports_stepping_granularity = self @@ -2320,7 +2395,7 @@ impl Session { }, }; - self.thread_states.process_step(thread_id); + self.active_snapshot.thread_states.process_step(thread_id); self.request( command, Self::on_step_response::(thread_id), @@ -2335,6 +2410,8 @@ impl Session { granularity: SteppingGranularity, cx: &mut Context, ) { + self.go_back_to_history(None, cx); + let supports_single_thread_execution_requests = self.capabilities.supports_single_thread_execution_requests; let supports_stepping_granularity = self @@ -2350,7 +2427,7 @@ impl Session { }, }; - self.thread_states.process_step(thread_id); + self.active_snapshot.thread_states.process_step(thread_id); self.request( command, @@ -2365,9 +2442,9 @@ impl Session { thread_id: ThreadId, cx: &mut Context, ) -> Result> { - if self.thread_states.thread_status(thread_id) == ThreadStatus::Stopped + if self.active_snapshot.thread_states.thread_status(thread_id) == ThreadStatus::Stopped && self.requests.contains_key(&ThreadsCommand.type_id()) - && self.threads.contains_key(&thread_id) + && self.active_snapshot.threads.contains_key(&thread_id) // ^ todo(debugger): We need a better way to check that we're not querying stale data // We could still be using an old thread id and have sent a new thread's request // This isn't the biggest concern right now because it hasn't caused any issues outside of tests @@ -2381,7 +2458,8 @@ impl Session { }, move |this, stack_frames, cx| { let entry = - this.threads + this.active_snapshot + .threads .entry(thread_id) .and_modify(|thread| match &stack_frames { Ok(stack_frames) => { @@ -2394,7 +2472,7 @@ impl Session { } Err(error) => { thread.stack_frames.clear(); - thread.stack_frames_error = Some(error.cloned()); + thread.stack_frames_error = Some(error.to_string().into()); } }); debug_assert!( @@ -2402,7 +2480,7 @@ impl Session { "Sent request for thread_id that doesn't exist" ); if let Ok(stack_frames) = stack_frames { - this.stack_frames.extend( + this.active_snapshot.stack_frames.extend( stack_frames .into_iter() .filter(|frame| { @@ -2427,10 +2505,10 @@ impl Session { ); } - match self.threads.get(&thread_id) { + match self.active_snapshot.threads.get(&thread_id) { Some(thread) => { if let Some(error) = &thread.stack_frames_error { - Err(error.cloned()) + Err(anyhow!(error.to_string())) } else { Ok(thread.stack_frames.clone()) } @@ -2457,6 +2535,7 @@ impl Session { } let entry = this + .active_snapshot .stack_frames .entry(stack_frame_id) .and_modify(|stack_frame| { @@ -2474,7 +2553,8 @@ impl Session { ); } - self.stack_frames + self.session_state() + .stack_frames .get(&stack_frame_id) .map(|frame| frame.scopes.as_slice()) .unwrap_or_default() @@ -2486,7 +2566,8 @@ impl Session { globals: bool, locals: bool, ) -> Vec { - let Some(stack_frame) = self.stack_frames.get(&stack_frame_id) else { + let state = self.session_state(); + let Some(stack_frame) = state.stack_frames.get(&stack_frame_id) else { return Vec::new(); }; @@ -2497,7 +2578,7 @@ impl Session { (scope.name.to_lowercase().contains("local") && locals) || (scope.name.to_lowercase().contains("global") && globals) }) - .filter_map(|scope| self.variables.get(&scope.variables_reference)) + .filter_map(|scope| state.variables.get(&scope.variables_reference)) .flatten() .cloned() .collect() @@ -2513,7 +2594,7 @@ impl Session { frame_id: u64, cx: &mut Context, ) -> Task> { - let request = self.mode.request_dap(EvaluateCommand { + let request = self.state.request_dap(EvaluateCommand { expression: expression.to_string(), context: Some(EvaluateArgumentsContext::Watch), frame_id: Some(frame_id), @@ -2570,7 +2651,9 @@ impl Session { return; }; - this.variables.insert(variables_reference, variables); + this.active_snapshot + .variables + .insert(variables_reference, variables); cx.emit(SessionEvent::Variables); cx.emit(SessionEvent::InvalidateInlineValue); @@ -2578,7 +2661,8 @@ impl Session { cx, ); - self.variables + self.session_state() + .variables .get(&variables_reference) .cloned() .unwrap_or_default() @@ -2645,7 +2729,7 @@ impl Session { location_reference: None, }; self.push_output(event); - let request = self.mode.request_dap(EvaluateCommand { + let request = self.state.request_dap(EvaluateCommand { expression, context, frame_id, @@ -2705,15 +2789,15 @@ impl Session { let Some(response) = response.log_err() else { return; }; - this.locations.insert(reference, response); + this.active_snapshot.locations.insert(reference, response); }, cx, ); - self.locations.get(&reference).cloned() + self.session_state().locations.get(&reference).cloned() } pub fn is_attached(&self) -> bool { - let SessionState::Running(local_mode) = &self.mode else { + let SessionState::Running(local_mode) = &self.state else { return false; }; local_mode.binary.request_args.request == StartDebuggingRequestArgumentsRequest::Attach @@ -2749,7 +2833,7 @@ impl Session { } pub fn thread_state(&self, thread_id: ThreadId) -> Option { - self.thread_states.thread_state(thread_id) + self.session_state().thread_states.thread_state(thread_id) } pub fn quirks(&self) -> SessionQuirks {