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