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