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