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