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