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