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 HistoricSnapshotSelected,
812}
813
814#[derive(Clone, Debug, PartialEq, Eq)]
815pub enum SessionStateEvent {
816 Running,
817 Shutdown,
818 Restart,
819 SpawnChildSession {
820 request: StartDebuggingRequestArguments,
821 },
822}
823
824impl EventEmitter<SessionEvent> for Session {}
825impl EventEmitter<SessionStateEvent> for Session {}
826
827// local session will send breakpoint updates to DAP for all new breakpoints
828// remote side will only send breakpoint updates when it is a breakpoint created by that peer
829// BreakpointStore notifies session on breakpoint changes
830impl Session {
831 pub(crate) fn new(
832 breakpoint_store: Entity<BreakpointStore>,
833 session_id: SessionId,
834 parent_session: Option<Entity<Session>>,
835 label: Option<SharedString>,
836 adapter: DebugAdapterName,
837 task_context: TaskContext,
838 quirks: SessionQuirks,
839 remote_client: Option<Entity<RemoteClient>>,
840 node_runtime: Option<NodeRuntime>,
841 http_client: Option<Arc<dyn HttpClient>>,
842 cx: &mut App,
843 ) -> Entity<Self> {
844 cx.new::<Self>(|cx| {
845 cx.subscribe(&breakpoint_store, |this, store, event, cx| match event {
846 BreakpointStoreEvent::BreakpointsUpdated(path, reason) => {
847 if let Some(local) = (!this.ignore_breakpoints)
848 .then(|| this.as_running_mut())
849 .flatten()
850 {
851 local
852 .send_breakpoints_from_path(path.clone(), *reason, &store, cx)
853 .detach();
854 };
855 }
856 BreakpointStoreEvent::BreakpointsCleared(paths) => {
857 if let Some(local) = (!this.ignore_breakpoints)
858 .then(|| this.as_running_mut())
859 .flatten()
860 {
861 local.unset_breakpoints_from_paths(paths, cx).detach();
862 }
863 }
864 BreakpointStoreEvent::SetDebugLine | BreakpointStoreEvent::ClearDebugLines => {}
865 })
866 .detach();
867
868 Self {
869 state: SessionState::Booting(None),
870 snapshots: VecDeque::with_capacity(DEBUG_HISTORY_LIMIT),
871 selected_snapshot_index: None,
872 active_snapshot: Default::default(),
873 id: session_id,
874 child_session_ids: HashSet::default(),
875 parent_session,
876 capabilities: Capabilities::default(),
877 watchers: HashMap::default(),
878 output_token: OutputToken(0),
879 output: circular_buffer::CircularBuffer::boxed(),
880 requests: HashMap::default(),
881 background_tasks: Vec::default(),
882 restart_task: None,
883 is_session_terminated: false,
884 ignore_breakpoints: false,
885 breakpoint_store,
886 data_breakpoints: Default::default(),
887 exception_breakpoints: Default::default(),
888 label,
889 adapter,
890 task_context,
891 memory: memory::Memory::new(),
892 quirks,
893 remote_client,
894 node_runtime,
895 http_client,
896 companion_port: None,
897 }
898 })
899 }
900
901 pub fn task_context(&self) -> &TaskContext {
902 &self.task_context
903 }
904
905 pub fn worktree(&self) -> Option<Entity<Worktree>> {
906 match &self.state {
907 SessionState::Booting(_) => None,
908 SessionState::Running(local_mode) => local_mode.worktree.upgrade(),
909 }
910 }
911
912 pub fn boot(
913 &mut self,
914 binary: DebugAdapterBinary,
915 worktree: Entity<Worktree>,
916 dap_store: WeakEntity<DapStore>,
917 cx: &mut Context<Self>,
918 ) -> Task<Result<()>> {
919 let (message_tx, mut message_rx) = futures::channel::mpsc::unbounded();
920 let (initialized_tx, initialized_rx) = futures::channel::oneshot::channel();
921
922 let background_tasks = vec![cx.spawn(async move |this: WeakEntity<Session>, cx| {
923 let mut initialized_tx = Some(initialized_tx);
924 while let Some(message) = message_rx.next().await {
925 if let Message::Event(event) = message {
926 if let Events::Initialized(_) = *event {
927 if let Some(tx) = initialized_tx.take() {
928 tx.send(()).ok();
929 }
930 } else {
931 let Ok(_) = this.update(cx, |session, cx| {
932 session.handle_dap_event(event, cx);
933 }) else {
934 break;
935 };
936 }
937 } else if let Message::Request(request) = message {
938 let Ok(_) = this.update(cx, |this, cx| {
939 if request.command == StartDebugging::COMMAND {
940 this.handle_start_debugging_request(request, cx)
941 .detach_and_log_err(cx);
942 } else if request.command == RunInTerminal::COMMAND {
943 this.handle_run_in_terminal_request(request, cx)
944 .detach_and_log_err(cx);
945 }
946 }) else {
947 break;
948 };
949 }
950 }
951 })];
952 self.background_tasks = background_tasks;
953 let id = self.id;
954 let parent_session = self.parent_session.clone();
955
956 cx.spawn(async move |this, cx| {
957 let mode = RunningMode::new(
958 id,
959 parent_session,
960 worktree.downgrade(),
961 binary.clone(),
962 message_tx,
963 cx,
964 )
965 .await?;
966 this.update(cx, |this, cx| {
967 match &mut this.state {
968 SessionState::Booting(task) if task.is_some() => {
969 task.take().unwrap().detach_and_log_err(cx);
970 }
971 SessionState::Booting(_) => {}
972 SessionState::Running(_) => {
973 debug_panic!("Attempting to boot a session that is already running");
974 }
975 };
976 this.state = SessionState::Running(mode);
977 cx.emit(SessionStateEvent::Running);
978 })?;
979
980 this.update(cx, |session, cx| session.request_initialize(cx))?
981 .await?;
982
983 let result = this
984 .update(cx, |session, cx| {
985 session.initialize_sequence(initialized_rx, dap_store.clone(), cx)
986 })?
987 .await;
988
989 if result.is_err() {
990 let mut console = this.update(cx, |session, cx| session.console_output(cx))?;
991
992 console
993 .send(format!(
994 "Tried to launch debugger with: {}",
995 serde_json::to_string_pretty(&binary.request_args.configuration)
996 .unwrap_or_default(),
997 ))
998 .await
999 .ok();
1000 }
1001
1002 result
1003 })
1004 }
1005
1006 pub fn session_id(&self) -> SessionId {
1007 self.id
1008 }
1009
1010 pub fn child_session_ids(&self) -> HashSet<SessionId> {
1011 self.child_session_ids.clone()
1012 }
1013
1014 pub fn add_child_session_id(&mut self, session_id: SessionId) {
1015 self.child_session_ids.insert(session_id);
1016 }
1017
1018 pub fn remove_child_session_id(&mut self, session_id: SessionId) {
1019 self.child_session_ids.remove(&session_id);
1020 }
1021
1022 pub fn parent_id(&self, cx: &App) -> Option<SessionId> {
1023 self.parent_session
1024 .as_ref()
1025 .map(|session| session.read(cx).id)
1026 }
1027
1028 pub fn parent_session(&self) -> Option<&Entity<Self>> {
1029 self.parent_session.as_ref()
1030 }
1031
1032 pub fn on_app_quit(&mut self, cx: &mut Context<Self>) -> Task<()> {
1033 let Some(client) = self.adapter_client() else {
1034 return Task::ready(());
1035 };
1036
1037 let supports_terminate = self
1038 .capabilities
1039 .support_terminate_debuggee
1040 .unwrap_or(false);
1041
1042 cx.background_spawn(async move {
1043 if supports_terminate {
1044 client
1045 .request::<dap::requests::Terminate>(dap::TerminateArguments {
1046 restart: Some(false),
1047 })
1048 .await
1049 .ok();
1050 } else {
1051 client
1052 .request::<dap::requests::Disconnect>(dap::DisconnectArguments {
1053 restart: Some(false),
1054 terminate_debuggee: Some(true),
1055 suspend_debuggee: Some(false),
1056 })
1057 .await
1058 .ok();
1059 }
1060 })
1061 }
1062
1063 pub fn capabilities(&self) -> &Capabilities {
1064 &self.capabilities
1065 }
1066
1067 pub fn binary(&self) -> Option<&DebugAdapterBinary> {
1068 match &self.state {
1069 SessionState::Booting(_) => None,
1070 SessionState::Running(running_mode) => Some(&running_mode.binary),
1071 }
1072 }
1073
1074 pub fn adapter(&self) -> DebugAdapterName {
1075 self.adapter.clone()
1076 }
1077
1078 pub fn label(&self) -> Option<SharedString> {
1079 self.label.clone()
1080 }
1081
1082 pub fn is_terminated(&self) -> bool {
1083 self.is_session_terminated
1084 }
1085
1086 pub fn console_output(&mut self, cx: &mut Context<Self>) -> mpsc::UnboundedSender<String> {
1087 let (tx, mut rx) = mpsc::unbounded();
1088
1089 cx.spawn(async move |this, cx| {
1090 while let Some(output) = rx.next().await {
1091 this.update(cx, |this, _| {
1092 let event = dap::OutputEvent {
1093 category: None,
1094 output,
1095 group: None,
1096 variables_reference: None,
1097 source: None,
1098 line: None,
1099 column: None,
1100 data: None,
1101 location_reference: None,
1102 };
1103 this.push_output(event);
1104 })?;
1105 }
1106 anyhow::Ok(())
1107 })
1108 .detach();
1109
1110 tx
1111 }
1112
1113 pub fn is_started(&self) -> bool {
1114 match &self.state {
1115 SessionState::Booting(_) => false,
1116 SessionState::Running(running) => running.is_started,
1117 }
1118 }
1119
1120 pub fn is_building(&self) -> bool {
1121 matches!(self.state, SessionState::Booting(_))
1122 }
1123
1124 pub fn as_running_mut(&mut self) -> Option<&mut RunningMode> {
1125 match &mut self.state {
1126 SessionState::Running(local_mode) => Some(local_mode),
1127 SessionState::Booting(_) => None,
1128 }
1129 }
1130
1131 pub fn as_running(&self) -> Option<&RunningMode> {
1132 match &self.state {
1133 SessionState::Running(local_mode) => Some(local_mode),
1134 SessionState::Booting(_) => None,
1135 }
1136 }
1137
1138 fn handle_start_debugging_request(
1139 &mut self,
1140 request: dap::messages::Request,
1141 cx: &mut Context<Self>,
1142 ) -> Task<Result<()>> {
1143 let request_seq = request.seq;
1144
1145 let launch_request: Option<Result<StartDebuggingRequestArguments, _>> = request
1146 .arguments
1147 .as_ref()
1148 .map(|value| serde_json::from_value(value.clone()));
1149
1150 let mut success = true;
1151 if let Some(Ok(request)) = launch_request {
1152 cx.emit(SessionStateEvent::SpawnChildSession { request });
1153 } else {
1154 log::error!(
1155 "Failed to parse launch request arguments: {:?}",
1156 request.arguments
1157 );
1158 success = false;
1159 }
1160
1161 cx.spawn(async move |this, cx| {
1162 this.update(cx, |this, cx| {
1163 this.respond_to_client(
1164 request_seq,
1165 success,
1166 StartDebugging::COMMAND.to_string(),
1167 None,
1168 cx,
1169 )
1170 })?
1171 .await
1172 })
1173 }
1174
1175 fn handle_run_in_terminal_request(
1176 &mut self,
1177 request: dap::messages::Request,
1178 cx: &mut Context<Self>,
1179 ) -> Task<Result<()>> {
1180 let request_args = match serde_json::from_value::<RunInTerminalRequestArguments>(
1181 request.arguments.unwrap_or_default(),
1182 ) {
1183 Ok(args) => args,
1184 Err(error) => {
1185 return cx.spawn(async move |session, cx| {
1186 let error = serde_json::to_value(dap::ErrorResponse {
1187 error: Some(dap::Message {
1188 id: request.seq,
1189 format: error.to_string(),
1190 variables: None,
1191 send_telemetry: None,
1192 show_user: None,
1193 url: None,
1194 url_label: None,
1195 }),
1196 })
1197 .ok();
1198
1199 session
1200 .update(cx, |this, cx| {
1201 this.respond_to_client(
1202 request.seq,
1203 false,
1204 StartDebugging::COMMAND.to_string(),
1205 error,
1206 cx,
1207 )
1208 })?
1209 .await?;
1210
1211 Err(anyhow!("Failed to parse RunInTerminalRequestArguments"))
1212 });
1213 }
1214 };
1215
1216 let seq = request.seq;
1217
1218 let (tx, mut rx) = mpsc::channel::<Result<u32>>(1);
1219 cx.emit(SessionEvent::RunInTerminal {
1220 request: request_args,
1221 sender: tx,
1222 });
1223 cx.notify();
1224
1225 cx.spawn(async move |session, cx| {
1226 let result = util::maybe!(async move {
1227 rx.next().await.ok_or_else(|| {
1228 anyhow!("failed to receive response from spawn terminal".to_string())
1229 })?
1230 })
1231 .await;
1232 let (success, body) = match result {
1233 Ok(pid) => (
1234 true,
1235 serde_json::to_value(dap::RunInTerminalResponse {
1236 process_id: None,
1237 shell_process_id: Some(pid as u64),
1238 })
1239 .ok(),
1240 ),
1241 Err(error) => (
1242 false,
1243 serde_json::to_value(dap::ErrorResponse {
1244 error: Some(dap::Message {
1245 id: seq,
1246 format: error.to_string(),
1247 variables: None,
1248 send_telemetry: None,
1249 show_user: None,
1250 url: None,
1251 url_label: None,
1252 }),
1253 })
1254 .ok(),
1255 ),
1256 };
1257
1258 session
1259 .update(cx, |session, cx| {
1260 session.respond_to_client(
1261 seq,
1262 success,
1263 RunInTerminal::COMMAND.to_string(),
1264 body,
1265 cx,
1266 )
1267 })?
1268 .await
1269 })
1270 }
1271
1272 pub(super) fn request_initialize(&mut self, cx: &mut Context<Self>) -> Task<Result<()>> {
1273 let adapter_id = self.adapter().to_string();
1274 let request = Initialize { adapter_id };
1275
1276 let SessionState::Running(running) = &self.state else {
1277 return Task::ready(Err(anyhow!(
1278 "Cannot send initialize request, task still building"
1279 )));
1280 };
1281 let mut response = running.request(request.clone());
1282
1283 cx.spawn(async move |this, cx| {
1284 loop {
1285 let capabilities = response.await;
1286 match capabilities {
1287 Err(e) => {
1288 let Ok(Some(reconnect)) = this.update(cx, |this, cx| {
1289 this.as_running()
1290 .and_then(|running| running.reconnect_for_ssh(&mut cx.to_async()))
1291 }) else {
1292 return Err(e);
1293 };
1294 log::info!("Failed to connect to debug adapter: {}, retrying...", e);
1295 reconnect.await?;
1296
1297 let Ok(Some(r)) = this.update(cx, |this, _| {
1298 this.as_running()
1299 .map(|running| running.request(request.clone()))
1300 }) else {
1301 return Err(e);
1302 };
1303 response = r
1304 }
1305 Ok(capabilities) => {
1306 this.update(cx, |session, cx| {
1307 session.capabilities = capabilities;
1308
1309 cx.emit(SessionEvent::CapabilitiesLoaded);
1310 })?;
1311 return Ok(());
1312 }
1313 }
1314 }
1315 })
1316 }
1317
1318 pub(super) fn initialize_sequence(
1319 &mut self,
1320 initialize_rx: oneshot::Receiver<()>,
1321 dap_store: WeakEntity<DapStore>,
1322 cx: &mut Context<Self>,
1323 ) -> Task<Result<()>> {
1324 match &self.state {
1325 SessionState::Running(local_mode) => {
1326 local_mode.initialize_sequence(&self.capabilities, initialize_rx, dap_store, cx)
1327 }
1328 SessionState::Booting(_) => {
1329 Task::ready(Err(anyhow!("cannot initialize, still building")))
1330 }
1331 }
1332 }
1333
1334 pub fn run_to_position(
1335 &mut self,
1336 breakpoint: SourceBreakpoint,
1337 active_thread_id: ThreadId,
1338 cx: &mut Context<Self>,
1339 ) {
1340 match &mut self.state {
1341 SessionState::Running(local_mode) => {
1342 if !matches!(
1343 self.active_snapshot
1344 .thread_states
1345 .thread_state(active_thread_id),
1346 Some(ThreadStatus::Stopped)
1347 ) {
1348 return;
1349 };
1350 let path = breakpoint.path.clone();
1351 local_mode.tmp_breakpoint = Some(breakpoint);
1352 let task = local_mode.send_breakpoints_from_path(
1353 path,
1354 BreakpointUpdatedReason::Toggled,
1355 &self.breakpoint_store,
1356 cx,
1357 );
1358
1359 cx.spawn(async move |this, cx| {
1360 task.await;
1361 this.update(cx, |this, cx| {
1362 this.continue_thread(active_thread_id, cx);
1363 })
1364 })
1365 .detach();
1366 }
1367 SessionState::Booting(_) => {}
1368 }
1369 }
1370
1371 pub fn has_new_output(&self, last_update: OutputToken) -> bool {
1372 self.output_token.0.checked_sub(last_update.0).unwrap_or(0) != 0
1373 }
1374
1375 pub fn output(
1376 &self,
1377 since: OutputToken,
1378 ) -> (impl Iterator<Item = &dap::OutputEvent>, OutputToken) {
1379 if self.output_token.0 == 0 {
1380 return (self.output.range(0..0), OutputToken(0));
1381 };
1382
1383 let events_since = self.output_token.0.checked_sub(since.0).unwrap_or(0);
1384
1385 let clamped_events_since = events_since.clamp(0, self.output.len());
1386 (
1387 self.output
1388 .range(self.output.len() - clamped_events_since..),
1389 self.output_token,
1390 )
1391 }
1392
1393 pub fn respond_to_client(
1394 &self,
1395 request_seq: u64,
1396 success: bool,
1397 command: String,
1398 body: Option<serde_json::Value>,
1399 cx: &mut Context<Self>,
1400 ) -> Task<Result<()>> {
1401 let Some(local_session) = self.as_running() else {
1402 unreachable!("Cannot respond to remote client");
1403 };
1404 let client = local_session.client.clone();
1405
1406 cx.background_spawn(async move {
1407 client
1408 .send_message(Message::Response(Response {
1409 body,
1410 success,
1411 command,
1412 seq: request_seq + 1,
1413 request_seq,
1414 message: None,
1415 }))
1416 .await
1417 })
1418 }
1419
1420 fn session_state(&self) -> &SessionSnapshot {
1421 self.selected_snapshot_index
1422 .and_then(|ix| self.snapshots.get(ix))
1423 .unwrap_or_else(|| &self.active_snapshot)
1424 }
1425
1426 fn push_to_history(&mut self) {
1427 if !self.has_ever_stopped() {
1428 return;
1429 }
1430
1431 while self.snapshots.len() >= DEBUG_HISTORY_LIMIT {
1432 self.snapshots.pop_front();
1433 }
1434
1435 self.snapshots
1436 .push_back(std::mem::take(&mut self.active_snapshot));
1437 }
1438
1439 pub fn history(&self) -> &VecDeque<SessionSnapshot> {
1440 &self.snapshots
1441 }
1442
1443 pub fn go_back_to_history(&mut self, ix: Option<usize>, cx: &mut Context<'_, Session>) {
1444 if self.selected_snapshot_index == ix {
1445 return;
1446 }
1447
1448 self.selected_snapshot_index = ix;
1449
1450 if ix.is_some() {
1451 cx.emit(SessionEvent::HistoricSnapshotSelected);
1452 }
1453
1454 cx.notify();
1455 }
1456
1457 pub fn active_history(&self) -> Option<usize> {
1458 self.selected_snapshot_index
1459 }
1460
1461 fn handle_stopped_event(&mut self, event: StoppedEvent, cx: &mut Context<Self>) {
1462 self.push_to_history();
1463
1464 self.state.stopped();
1465 // todo(debugger): Find a clean way to get around the clone
1466 let breakpoint_store = self.breakpoint_store.clone();
1467 if let Some((local, path)) = self.as_running_mut().and_then(|local| {
1468 let breakpoint = local.tmp_breakpoint.take()?;
1469 let path = breakpoint.path;
1470 Some((local, path))
1471 }) {
1472 local
1473 .send_breakpoints_from_path(
1474 path,
1475 BreakpointUpdatedReason::Toggled,
1476 &breakpoint_store,
1477 cx,
1478 )
1479 .detach();
1480 };
1481
1482 if event.all_threads_stopped.unwrap_or_default() || event.thread_id.is_none() {
1483 self.active_snapshot.thread_states.stop_all_threads();
1484 self.invalidate_command_type::<StackTraceCommand>();
1485 }
1486
1487 // Event if we stopped all threads we still need to insert the thread_id
1488 // to our own data
1489 if let Some(thread_id) = event.thread_id {
1490 self.active_snapshot
1491 .thread_states
1492 .stop_thread(ThreadId(thread_id));
1493
1494 self.invalidate_state(
1495 &StackTraceCommand {
1496 thread_id,
1497 start_frame: None,
1498 levels: None,
1499 }
1500 .into(),
1501 );
1502 }
1503
1504 self.invalidate_generic();
1505 self.active_snapshot.threads.clear();
1506 self.active_snapshot.variables.clear();
1507 cx.emit(SessionEvent::Stopped(
1508 event
1509 .thread_id
1510 .map(Into::into)
1511 .filter(|_| !event.preserve_focus_hint.unwrap_or(false)),
1512 ));
1513 cx.emit(SessionEvent::InvalidateInlineValue);
1514 cx.notify();
1515 }
1516
1517 pub(crate) fn handle_dap_event(&mut self, event: Box<Events>, cx: &mut Context<Self>) {
1518 match *event {
1519 Events::Initialized(_) => {
1520 debug_assert!(
1521 false,
1522 "Initialized event should have been handled in LocalMode"
1523 );
1524 }
1525 Events::Stopped(event) => self.handle_stopped_event(event, cx),
1526 Events::Continued(event) => {
1527 if event.all_threads_continued.unwrap_or_default() {
1528 self.active_snapshot.thread_states.continue_all_threads();
1529 self.breakpoint_store.update(cx, |store, cx| {
1530 store.remove_active_position(Some(self.session_id()), cx)
1531 });
1532 } else {
1533 self.active_snapshot
1534 .thread_states
1535 .continue_thread(ThreadId(event.thread_id));
1536 }
1537 // todo(debugger): We should be able to get away with only invalidating generic if all threads were continued
1538 self.invalidate_generic();
1539 }
1540 Events::Exited(_event) => {
1541 self.clear_active_debug_line(cx);
1542 }
1543 Events::Terminated(_) => {
1544 self.shutdown(cx).detach();
1545 }
1546 Events::Thread(event) => {
1547 let thread_id = ThreadId(event.thread_id);
1548
1549 match event.reason {
1550 dap::ThreadEventReason::Started => {
1551 self.active_snapshot
1552 .thread_states
1553 .continue_thread(thread_id);
1554 }
1555 dap::ThreadEventReason::Exited => {
1556 self.active_snapshot.thread_states.exit_thread(thread_id);
1557 }
1558 reason => {
1559 log::error!("Unhandled thread event reason {:?}", reason);
1560 }
1561 }
1562 self.invalidate_state(&ThreadsCommand.into());
1563 cx.notify();
1564 }
1565 Events::Output(event) => {
1566 if event
1567 .category
1568 .as_ref()
1569 .is_some_and(|category| *category == OutputEventCategory::Telemetry)
1570 {
1571 return;
1572 }
1573
1574 self.push_output(event);
1575 cx.notify();
1576 }
1577 Events::Breakpoint(event) => self.breakpoint_store.update(cx, |store, _| {
1578 store.update_session_breakpoint(self.session_id(), event.reason, event.breakpoint);
1579 }),
1580 Events::Module(event) => {
1581 match event.reason {
1582 dap::ModuleEventReason::New => {
1583 self.active_snapshot.modules.push(event.module);
1584 }
1585 dap::ModuleEventReason::Changed => {
1586 if let Some(module) = self
1587 .active_snapshot
1588 .modules
1589 .iter_mut()
1590 .find(|other| event.module.id == other.id)
1591 {
1592 *module = event.module;
1593 }
1594 }
1595 dap::ModuleEventReason::Removed => {
1596 self.active_snapshot
1597 .modules
1598 .retain(|other| event.module.id != other.id);
1599 }
1600 }
1601
1602 // todo(debugger): We should only send the invalidate command to downstream clients.
1603 // self.invalidate_state(&ModulesCommand.into());
1604 }
1605 Events::LoadedSource(_) => {
1606 self.invalidate_state(&LoadedSourcesCommand.into());
1607 }
1608 Events::Capabilities(event) => {
1609 self.capabilities = self.capabilities.merge(event.capabilities);
1610
1611 // The adapter might've enabled new exception breakpoints (or disabled existing ones).
1612 let recent_filters = self
1613 .capabilities
1614 .exception_breakpoint_filters
1615 .iter()
1616 .flatten()
1617 .map(|filter| (filter.filter.clone(), filter.clone()))
1618 .collect::<BTreeMap<_, _>>();
1619 for filter in recent_filters.values() {
1620 let default = filter.default.unwrap_or_default();
1621 self.exception_breakpoints
1622 .entry(filter.filter.clone())
1623 .or_insert_with(|| (filter.clone(), default));
1624 }
1625 self.exception_breakpoints
1626 .retain(|k, _| recent_filters.contains_key(k));
1627 if self.is_started() {
1628 self.send_exception_breakpoints(cx);
1629 }
1630
1631 // Remove the ones that no longer exist.
1632 cx.notify();
1633 }
1634 Events::Memory(_) => {}
1635 Events::Process(_) => {}
1636 Events::ProgressEnd(_) => {}
1637 Events::ProgressStart(_) => {}
1638 Events::ProgressUpdate(_) => {}
1639 Events::Invalidated(_) => {}
1640 Events::Other(event) => {
1641 if event.event == "launchBrowserInCompanion" {
1642 let Some(request) = serde_json::from_value(event.body).ok() else {
1643 log::error!("failed to deserialize launchBrowserInCompanion event");
1644 return;
1645 };
1646 self.launch_browser_for_remote_server(request, cx);
1647 } else if event.event == "killCompanionBrowser" {
1648 let Some(request) = serde_json::from_value(event.body).ok() else {
1649 log::error!("failed to deserialize killCompanionBrowser event");
1650 return;
1651 };
1652 self.kill_browser(request, cx);
1653 }
1654 }
1655 }
1656 }
1657
1658 /// 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.
1659 fn fetch<T: LocalDapCommand + PartialEq + Eq + Hash>(
1660 &mut self,
1661 request: T,
1662 process_result: impl FnOnce(&mut Self, Result<T::Response>, &mut Context<Self>) + 'static,
1663 cx: &mut Context<Self>,
1664 ) {
1665 const {
1666 assert!(
1667 T::CACHEABLE,
1668 "Only requests marked as cacheable should invoke `fetch`"
1669 );
1670 }
1671
1672 if (!self.active_snapshot.thread_states.any_stopped_thread()
1673 && request.type_id() != TypeId::of::<ThreadsCommand>())
1674 || self.selected_snapshot_index.is_some()
1675 || self.is_session_terminated
1676 {
1677 return;
1678 }
1679
1680 let request_map = self
1681 .requests
1682 .entry(std::any::TypeId::of::<T>())
1683 .or_default();
1684
1685 if let Entry::Vacant(vacant) = request_map.entry(request.into()) {
1686 let command = vacant.key().0.clone().as_any_arc().downcast::<T>().unwrap();
1687
1688 let task = Self::request_inner::<Arc<T>>(
1689 &self.capabilities,
1690 &self.state,
1691 command,
1692 |this, result, cx| {
1693 process_result(this, result, cx);
1694 None
1695 },
1696 cx,
1697 );
1698 let task = cx
1699 .background_executor()
1700 .spawn(async move {
1701 let _ = task.await?;
1702 Some(())
1703 })
1704 .shared();
1705
1706 vacant.insert(task);
1707 cx.notify();
1708 }
1709 }
1710
1711 fn request_inner<T: LocalDapCommand + PartialEq + Eq + Hash>(
1712 capabilities: &Capabilities,
1713 mode: &SessionState,
1714 request: T,
1715 process_result: impl FnOnce(
1716 &mut Self,
1717 Result<T::Response>,
1718 &mut Context<Self>,
1719 ) -> Option<T::Response>
1720 + 'static,
1721 cx: &mut Context<Self>,
1722 ) -> Task<Option<T::Response>> {
1723 if !T::is_supported(capabilities) {
1724 log::warn!(
1725 "Attempted to send a DAP request that isn't supported: {:?}",
1726 request
1727 );
1728 let error = Err(anyhow::Error::msg(
1729 "Couldn't complete request because it's not supported",
1730 ));
1731 return cx.spawn(async move |this, cx| {
1732 this.update(cx, |this, cx| process_result(this, error, cx))
1733 .ok()
1734 .flatten()
1735 });
1736 }
1737
1738 let request = mode.request_dap(request);
1739 cx.spawn(async move |this, cx| {
1740 let result = request.await;
1741 this.update(cx, |this, cx| process_result(this, result, cx))
1742 .ok()
1743 .flatten()
1744 })
1745 }
1746
1747 fn request<T: LocalDapCommand + PartialEq + Eq + Hash>(
1748 &self,
1749 request: T,
1750 process_result: impl FnOnce(
1751 &mut Self,
1752 Result<T::Response>,
1753 &mut Context<Self>,
1754 ) -> Option<T::Response>
1755 + 'static,
1756 cx: &mut Context<Self>,
1757 ) -> Task<Option<T::Response>> {
1758 Self::request_inner(&self.capabilities, &self.state, request, process_result, cx)
1759 }
1760
1761 fn invalidate_command_type<Command: LocalDapCommand>(&mut self) {
1762 self.requests.remove(&std::any::TypeId::of::<Command>());
1763 }
1764
1765 fn invalidate_generic(&mut self) {
1766 self.invalidate_command_type::<ModulesCommand>();
1767 self.invalidate_command_type::<LoadedSourcesCommand>();
1768 self.invalidate_command_type::<ThreadsCommand>();
1769 self.invalidate_command_type::<DataBreakpointInfoCommand>();
1770 self.invalidate_command_type::<ReadMemory>();
1771 let executor = self.as_running().map(|running| running.executor.clone());
1772 if let Some(executor) = executor {
1773 self.memory.clear(&executor);
1774 }
1775 }
1776
1777 fn invalidate_state(&mut self, key: &RequestSlot) {
1778 self.requests
1779 .entry((&*key.0 as &dyn Any).type_id())
1780 .and_modify(|request_map| {
1781 request_map.remove(key);
1782 });
1783 }
1784
1785 fn push_output(&mut self, event: OutputEvent) {
1786 self.output.push_back(event);
1787 self.output_token.0 += 1;
1788 }
1789
1790 pub fn any_stopped_thread(&self) -> bool {
1791 self.active_snapshot.thread_states.any_stopped_thread()
1792 }
1793
1794 pub fn thread_status(&self, thread_id: ThreadId) -> ThreadStatus {
1795 self.active_snapshot.thread_states.thread_status(thread_id)
1796 }
1797
1798 pub fn threads(&mut self, cx: &mut Context<Self>) -> Vec<(dap::Thread, ThreadStatus)> {
1799 self.fetch(
1800 dap_command::ThreadsCommand,
1801 |this, result, cx| {
1802 let Some(result) = result.log_err() else {
1803 return;
1804 };
1805
1806 this.active_snapshot.threads = result
1807 .into_iter()
1808 .map(|thread| (ThreadId(thread.id), Thread::from(thread)))
1809 .collect();
1810
1811 this.invalidate_command_type::<StackTraceCommand>();
1812 cx.emit(SessionEvent::Threads);
1813 cx.notify();
1814 },
1815 cx,
1816 );
1817
1818 let state = self.session_state();
1819 state
1820 .threads
1821 .values()
1822 .map(|thread| {
1823 (
1824 thread.dap.clone(),
1825 state.thread_states.thread_status(ThreadId(thread.dap.id)),
1826 )
1827 })
1828 .collect()
1829 }
1830
1831 pub fn modules(&mut self, cx: &mut Context<Self>) -> &[Module] {
1832 self.fetch(
1833 dap_command::ModulesCommand,
1834 |this, result, cx| {
1835 let Some(result) = result.log_err() else {
1836 return;
1837 };
1838
1839 this.active_snapshot.modules = result;
1840 cx.emit(SessionEvent::Modules);
1841 cx.notify();
1842 },
1843 cx,
1844 );
1845
1846 &self.session_state().modules
1847 }
1848
1849 // CodeLLDB returns the size of a pointed-to-memory, which we can use to make the experience of go-to-memory better.
1850 pub fn data_access_size(
1851 &mut self,
1852 frame_id: Option<u64>,
1853 evaluate_name: &str,
1854 cx: &mut Context<Self>,
1855 ) -> Task<Option<u64>> {
1856 let request = self.request(
1857 EvaluateCommand {
1858 expression: format!("?${{sizeof({evaluate_name})}}"),
1859 frame_id,
1860
1861 context: Some(EvaluateArgumentsContext::Repl),
1862 source: None,
1863 },
1864 |_, response, _| response.ok(),
1865 cx,
1866 );
1867 cx.background_spawn(async move {
1868 let result = request.await?;
1869 result.result.parse().ok()
1870 })
1871 }
1872
1873 pub fn memory_reference_of_expr(
1874 &mut self,
1875 frame_id: Option<u64>,
1876 expression: String,
1877 cx: &mut Context<Self>,
1878 ) -> Task<Option<(String, Option<String>)>> {
1879 let request = self.request(
1880 EvaluateCommand {
1881 expression,
1882 frame_id,
1883
1884 context: Some(EvaluateArgumentsContext::Repl),
1885 source: None,
1886 },
1887 |_, response, _| response.ok(),
1888 cx,
1889 );
1890 cx.background_spawn(async move {
1891 let result = request.await?;
1892 result
1893 .memory_reference
1894 .map(|reference| (reference, result.type_))
1895 })
1896 }
1897
1898 pub fn write_memory(&mut self, address: u64, data: &[u8], cx: &mut Context<Self>) {
1899 let data = base64::engine::general_purpose::STANDARD.encode(data);
1900 self.request(
1901 WriteMemoryArguments {
1902 memory_reference: address.to_string(),
1903 data,
1904 allow_partial: None,
1905 offset: None,
1906 },
1907 |this, response, cx| {
1908 this.memory.clear(cx.background_executor());
1909 this.invalidate_command_type::<ReadMemory>();
1910 this.invalidate_command_type::<VariablesCommand>();
1911 cx.emit(SessionEvent::Variables);
1912 response.ok()
1913 },
1914 cx,
1915 )
1916 .detach();
1917 }
1918 pub fn read_memory(
1919 &mut self,
1920 range: RangeInclusive<u64>,
1921 cx: &mut Context<Self>,
1922 ) -> MemoryIterator {
1923 // This function is a bit more involved when it comes to fetching data.
1924 // Since we attempt to read memory in pages, we need to account for some parts
1925 // of memory being unreadable. Therefore, we start off by fetching a page per request.
1926 // In case that fails, we try to re-fetch smaller regions until we have the full range.
1927 let page_range = Memory::memory_range_to_page_range(range.clone());
1928 for page_address in PageAddress::iter_range(page_range) {
1929 self.read_single_page_memory(page_address, cx);
1930 }
1931 self.memory.memory_range(range)
1932 }
1933
1934 fn read_single_page_memory(&mut self, page_start: PageAddress, cx: &mut Context<Self>) {
1935 _ = maybe!({
1936 let builder = self.memory.build_page(page_start)?;
1937
1938 self.memory_read_fetch_page_recursive(builder, cx);
1939 Some(())
1940 });
1941 }
1942 fn memory_read_fetch_page_recursive(
1943 &mut self,
1944 mut builder: MemoryPageBuilder,
1945 cx: &mut Context<Self>,
1946 ) {
1947 let Some(next_request) = builder.next_request() else {
1948 // We're done fetching. Let's grab the page and insert it into our memory store.
1949 let (address, contents) = builder.build();
1950 self.memory.insert_page(address, contents);
1951
1952 return;
1953 };
1954 let size = next_request.size;
1955 self.fetch(
1956 ReadMemory {
1957 memory_reference: format!("0x{:X}", next_request.address),
1958 offset: Some(0),
1959 count: next_request.size,
1960 },
1961 move |this, memory, cx| {
1962 if let Ok(memory) = memory {
1963 builder.known(memory.content);
1964 if let Some(unknown) = memory.unreadable_bytes {
1965 builder.unknown(unknown);
1966 }
1967 // This is the recursive bit: if we're not yet done with
1968 // the whole page, we'll kick off a new request with smaller range.
1969 // Note that this function is recursive only conceptually;
1970 // since it kicks off a new request with callback, we don't need to worry about stack overflow.
1971 this.memory_read_fetch_page_recursive(builder, cx);
1972 } else {
1973 builder.unknown(size);
1974 }
1975 },
1976 cx,
1977 );
1978 }
1979
1980 pub fn ignore_breakpoints(&self) -> bool {
1981 self.ignore_breakpoints
1982 }
1983
1984 pub fn toggle_ignore_breakpoints(
1985 &mut self,
1986 cx: &mut App,
1987 ) -> Task<HashMap<Arc<Path>, anyhow::Error>> {
1988 self.set_ignore_breakpoints(!self.ignore_breakpoints, cx)
1989 }
1990
1991 pub(crate) fn set_ignore_breakpoints(
1992 &mut self,
1993 ignore: bool,
1994 cx: &mut App,
1995 ) -> Task<HashMap<Arc<Path>, anyhow::Error>> {
1996 if self.ignore_breakpoints == ignore {
1997 return Task::ready(HashMap::default());
1998 }
1999
2000 self.ignore_breakpoints = ignore;
2001
2002 if let Some(local) = self.as_running() {
2003 local.send_source_breakpoints(ignore, &self.breakpoint_store, cx)
2004 } else {
2005 // todo(debugger): We need to propagate this change to downstream sessions and send a message to upstream sessions
2006 unimplemented!()
2007 }
2008 }
2009
2010 pub fn data_breakpoints(&self) -> impl Iterator<Item = &DataBreakpointState> {
2011 self.data_breakpoints.values()
2012 }
2013
2014 pub fn exception_breakpoints(
2015 &self,
2016 ) -> impl Iterator<Item = &(ExceptionBreakpointsFilter, IsEnabled)> {
2017 self.exception_breakpoints.values()
2018 }
2019
2020 pub fn toggle_exception_breakpoint(&mut self, id: &str, cx: &App) {
2021 if let Some((_, is_enabled)) = self.exception_breakpoints.get_mut(id) {
2022 *is_enabled = !*is_enabled;
2023 self.send_exception_breakpoints(cx);
2024 }
2025 }
2026
2027 fn send_exception_breakpoints(&mut self, cx: &App) {
2028 if let Some(local) = self.as_running() {
2029 let exception_filters = self
2030 .exception_breakpoints
2031 .values()
2032 .filter_map(|(filter, is_enabled)| is_enabled.then(|| filter.clone()))
2033 .collect();
2034
2035 let supports_exception_filters = self
2036 .capabilities
2037 .supports_exception_filter_options
2038 .unwrap_or_default();
2039 local
2040 .send_exception_breakpoints(exception_filters, supports_exception_filters)
2041 .detach_and_log_err(cx);
2042 } else {
2043 debug_assert!(false, "Not implemented");
2044 }
2045 }
2046
2047 pub fn toggle_data_breakpoint(&mut self, id: &str, cx: &mut Context<'_, Session>) {
2048 if let Some(state) = self.data_breakpoints.get_mut(id) {
2049 state.is_enabled = !state.is_enabled;
2050 self.send_exception_breakpoints(cx);
2051 }
2052 }
2053
2054 fn send_data_breakpoints(&mut self, cx: &mut Context<Self>) {
2055 if let Some(mode) = self.as_running() {
2056 let breakpoints = self
2057 .data_breakpoints
2058 .values()
2059 .filter_map(|state| state.is_enabled.then(|| state.dap.clone()))
2060 .collect();
2061 let command = SetDataBreakpointsCommand { breakpoints };
2062 mode.request(command).detach_and_log_err(cx);
2063 }
2064 }
2065
2066 pub fn create_data_breakpoint(
2067 &mut self,
2068 context: Arc<DataBreakpointContext>,
2069 data_id: String,
2070 dap: dap::DataBreakpoint,
2071 cx: &mut Context<Self>,
2072 ) {
2073 if self.data_breakpoints.remove(&data_id).is_none() {
2074 self.data_breakpoints.insert(
2075 data_id,
2076 DataBreakpointState {
2077 dap,
2078 is_enabled: true,
2079 context,
2080 },
2081 );
2082 }
2083 self.send_data_breakpoints(cx);
2084 }
2085
2086 pub fn breakpoints_enabled(&self) -> bool {
2087 self.ignore_breakpoints
2088 }
2089
2090 pub fn loaded_sources(&mut self, cx: &mut Context<Self>) -> &[Source] {
2091 self.fetch(
2092 dap_command::LoadedSourcesCommand,
2093 |this, result, cx| {
2094 let Some(result) = result.log_err() else {
2095 return;
2096 };
2097 this.active_snapshot.loaded_sources = result;
2098 cx.emit(SessionEvent::LoadedSources);
2099 cx.notify();
2100 },
2101 cx,
2102 );
2103 &self.session_state().loaded_sources
2104 }
2105
2106 fn fallback_to_manual_restart(
2107 &mut self,
2108 res: Result<()>,
2109 cx: &mut Context<Self>,
2110 ) -> Option<()> {
2111 if res.log_err().is_none() {
2112 cx.emit(SessionStateEvent::Restart);
2113 return None;
2114 }
2115 Some(())
2116 }
2117
2118 fn empty_response(&mut self, res: Result<()>, _cx: &mut Context<Self>) -> Option<()> {
2119 res.log_err()?;
2120 Some(())
2121 }
2122
2123 fn on_step_response<T: LocalDapCommand + PartialEq + Eq + Hash>(
2124 thread_id: ThreadId,
2125 ) -> impl FnOnce(&mut Self, Result<T::Response>, &mut Context<Self>) -> Option<T::Response> + 'static
2126 {
2127 move |this, response, cx| match response.log_err() {
2128 Some(response) => {
2129 this.breakpoint_store.update(cx, |store, cx| {
2130 store.remove_active_position(Some(this.session_id()), cx)
2131 });
2132 Some(response)
2133 }
2134 None => {
2135 this.active_snapshot.thread_states.stop_thread(thread_id);
2136 cx.notify();
2137 None
2138 }
2139 }
2140 }
2141
2142 fn clear_active_debug_line_response(
2143 &mut self,
2144 response: Result<()>,
2145 cx: &mut Context<Session>,
2146 ) -> Option<()> {
2147 response.log_err()?;
2148 self.clear_active_debug_line(cx);
2149 Some(())
2150 }
2151
2152 fn clear_active_debug_line(&mut self, cx: &mut Context<Session>) {
2153 self.breakpoint_store.update(cx, |store, cx| {
2154 store.remove_active_position(Some(self.id), cx)
2155 });
2156 }
2157
2158 pub fn pause_thread(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) {
2159 self.request(
2160 PauseCommand {
2161 thread_id: thread_id.0,
2162 },
2163 Self::empty_response,
2164 cx,
2165 )
2166 .detach();
2167 }
2168
2169 pub fn restart_stack_frame(&mut self, stack_frame_id: u64, cx: &mut Context<Self>) {
2170 self.request(
2171 RestartStackFrameCommand { stack_frame_id },
2172 Self::empty_response,
2173 cx,
2174 )
2175 .detach();
2176 }
2177
2178 pub fn restart(&mut self, args: Option<Value>, cx: &mut Context<Self>) {
2179 if self.restart_task.is_some() || self.as_running().is_none() {
2180 return;
2181 }
2182
2183 let supports_dap_restart =
2184 self.capabilities.supports_restart_request.unwrap_or(false) && !self.is_terminated();
2185
2186 self.restart_task = Some(cx.spawn(async move |this, cx| {
2187 let _ = this.update(cx, |session, cx| {
2188 if supports_dap_restart {
2189 session
2190 .request(
2191 RestartCommand {
2192 raw: args.unwrap_or(Value::Null),
2193 },
2194 Self::fallback_to_manual_restart,
2195 cx,
2196 )
2197 .detach();
2198 } else {
2199 cx.emit(SessionStateEvent::Restart);
2200 }
2201 });
2202 }));
2203 }
2204
2205 pub fn shutdown(&mut self, cx: &mut Context<Self>) -> Task<()> {
2206 if self.is_session_terminated {
2207 return Task::ready(());
2208 }
2209
2210 self.is_session_terminated = true;
2211 self.active_snapshot.thread_states.exit_all_threads();
2212 cx.notify();
2213
2214 let task = match &mut self.state {
2215 SessionState::Running(_) => {
2216 if self
2217 .capabilities
2218 .supports_terminate_request
2219 .unwrap_or_default()
2220 {
2221 self.request(
2222 TerminateCommand {
2223 restart: Some(false),
2224 },
2225 Self::clear_active_debug_line_response,
2226 cx,
2227 )
2228 } else {
2229 self.request(
2230 DisconnectCommand {
2231 restart: Some(false),
2232 terminate_debuggee: Some(true),
2233 suspend_debuggee: Some(false),
2234 },
2235 Self::clear_active_debug_line_response,
2236 cx,
2237 )
2238 }
2239 }
2240 SessionState::Booting(build_task) => {
2241 build_task.take();
2242 Task::ready(Some(()))
2243 }
2244 };
2245
2246 cx.emit(SessionStateEvent::Shutdown);
2247
2248 cx.spawn(async move |this, cx| {
2249 task.await;
2250 let _ = this.update(cx, |this, _| {
2251 if let Some(adapter_client) = this.adapter_client() {
2252 adapter_client.kill();
2253 }
2254 });
2255 })
2256 }
2257
2258 pub fn completions(
2259 &mut self,
2260 query: CompletionsQuery,
2261 cx: &mut Context<Self>,
2262 ) -> Task<Result<Vec<dap::CompletionItem>>> {
2263 let task = self.request(query, |_, result, _| result.log_err(), cx);
2264
2265 cx.background_executor().spawn(async move {
2266 anyhow::Ok(
2267 task.await
2268 .map(|response| response.targets)
2269 .context("failed to fetch completions")?,
2270 )
2271 })
2272 }
2273
2274 pub fn continue_thread(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) {
2275 self.go_back_to_history(None, cx);
2276
2277 let supports_single_thread_execution_requests =
2278 self.capabilities.supports_single_thread_execution_requests;
2279 self.active_snapshot
2280 .thread_states
2281 .continue_thread(thread_id);
2282 self.request(
2283 ContinueCommand {
2284 args: ContinueArguments {
2285 thread_id: thread_id.0,
2286 single_thread: supports_single_thread_execution_requests,
2287 },
2288 },
2289 Self::on_step_response::<ContinueCommand>(thread_id),
2290 cx,
2291 )
2292 .detach();
2293 }
2294
2295 pub fn adapter_client(&self) -> Option<Arc<DebugAdapterClient>> {
2296 match self.state {
2297 SessionState::Running(ref local) => Some(local.client.clone()),
2298 SessionState::Booting(_) => None,
2299 }
2300 }
2301
2302 pub fn has_ever_stopped(&self) -> bool {
2303 self.state.has_ever_stopped()
2304 }
2305
2306 pub fn step_over(
2307 &mut self,
2308 thread_id: ThreadId,
2309 granularity: SteppingGranularity,
2310 cx: &mut Context<Self>,
2311 ) {
2312 self.go_back_to_history(None, cx);
2313
2314 let supports_single_thread_execution_requests =
2315 self.capabilities.supports_single_thread_execution_requests;
2316 let supports_stepping_granularity = self
2317 .capabilities
2318 .supports_stepping_granularity
2319 .unwrap_or_default();
2320
2321 let command = NextCommand {
2322 inner: StepCommand {
2323 thread_id: thread_id.0,
2324 granularity: supports_stepping_granularity.then(|| granularity),
2325 single_thread: supports_single_thread_execution_requests,
2326 },
2327 };
2328
2329 self.active_snapshot.thread_states.process_step(thread_id);
2330 self.request(
2331 command,
2332 Self::on_step_response::<NextCommand>(thread_id),
2333 cx,
2334 )
2335 .detach();
2336 }
2337
2338 pub fn step_in(
2339 &mut self,
2340 thread_id: ThreadId,
2341 granularity: SteppingGranularity,
2342 cx: &mut Context<Self>,
2343 ) {
2344 self.go_back_to_history(None, cx);
2345
2346 let supports_single_thread_execution_requests =
2347 self.capabilities.supports_single_thread_execution_requests;
2348 let supports_stepping_granularity = self
2349 .capabilities
2350 .supports_stepping_granularity
2351 .unwrap_or_default();
2352
2353 let command = StepInCommand {
2354 inner: StepCommand {
2355 thread_id: thread_id.0,
2356 granularity: supports_stepping_granularity.then(|| granularity),
2357 single_thread: supports_single_thread_execution_requests,
2358 },
2359 };
2360
2361 self.active_snapshot.thread_states.process_step(thread_id);
2362 self.request(
2363 command,
2364 Self::on_step_response::<StepInCommand>(thread_id),
2365 cx,
2366 )
2367 .detach();
2368 }
2369
2370 pub fn step_out(
2371 &mut self,
2372 thread_id: ThreadId,
2373 granularity: SteppingGranularity,
2374 cx: &mut Context<Self>,
2375 ) {
2376 self.go_back_to_history(None, cx);
2377
2378 let supports_single_thread_execution_requests =
2379 self.capabilities.supports_single_thread_execution_requests;
2380 let supports_stepping_granularity = self
2381 .capabilities
2382 .supports_stepping_granularity
2383 .unwrap_or_default();
2384
2385 let command = StepOutCommand {
2386 inner: StepCommand {
2387 thread_id: thread_id.0,
2388 granularity: supports_stepping_granularity.then(|| granularity),
2389 single_thread: supports_single_thread_execution_requests,
2390 },
2391 };
2392
2393 self.active_snapshot.thread_states.process_step(thread_id);
2394 self.request(
2395 command,
2396 Self::on_step_response::<StepOutCommand>(thread_id),
2397 cx,
2398 )
2399 .detach();
2400 }
2401
2402 pub fn step_back(
2403 &mut self,
2404 thread_id: ThreadId,
2405 granularity: SteppingGranularity,
2406 cx: &mut Context<Self>,
2407 ) {
2408 self.go_back_to_history(None, cx);
2409
2410 let supports_single_thread_execution_requests =
2411 self.capabilities.supports_single_thread_execution_requests;
2412 let supports_stepping_granularity = self
2413 .capabilities
2414 .supports_stepping_granularity
2415 .unwrap_or_default();
2416
2417 let command = StepBackCommand {
2418 inner: StepCommand {
2419 thread_id: thread_id.0,
2420 granularity: supports_stepping_granularity.then(|| granularity),
2421 single_thread: supports_single_thread_execution_requests,
2422 },
2423 };
2424
2425 self.active_snapshot.thread_states.process_step(thread_id);
2426
2427 self.request(
2428 command,
2429 Self::on_step_response::<StepBackCommand>(thread_id),
2430 cx,
2431 )
2432 .detach();
2433 }
2434
2435 pub fn stack_frames(
2436 &mut self,
2437 thread_id: ThreadId,
2438 cx: &mut Context<Self>,
2439 ) -> Result<Vec<StackFrame>> {
2440 if self.active_snapshot.thread_states.thread_status(thread_id) == ThreadStatus::Stopped
2441 && self.requests.contains_key(&ThreadsCommand.type_id())
2442 && self.active_snapshot.threads.contains_key(&thread_id)
2443 // ^ todo(debugger): We need a better way to check that we're not querying stale data
2444 // We could still be using an old thread id and have sent a new thread's request
2445 // This isn't the biggest concern right now because it hasn't caused any issues outside of tests
2446 // But it very well could cause a minor bug in the future that is hard to track down
2447 {
2448 self.fetch(
2449 super::dap_command::StackTraceCommand {
2450 thread_id: thread_id.0,
2451 start_frame: None,
2452 levels: None,
2453 },
2454 move |this, stack_frames, cx| {
2455 let entry =
2456 this.active_snapshot
2457 .threads
2458 .entry(thread_id)
2459 .and_modify(|thread| match &stack_frames {
2460 Ok(stack_frames) => {
2461 thread.stack_frames = stack_frames
2462 .iter()
2463 .cloned()
2464 .map(StackFrame::from)
2465 .collect();
2466 thread.stack_frames_error = None;
2467 }
2468 Err(error) => {
2469 thread.stack_frames.clear();
2470 thread.stack_frames_error = Some(error.to_string().into());
2471 }
2472 });
2473 debug_assert!(
2474 matches!(entry, indexmap::map::Entry::Occupied(_)),
2475 "Sent request for thread_id that doesn't exist"
2476 );
2477 if let Ok(stack_frames) = stack_frames {
2478 this.active_snapshot.stack_frames.extend(
2479 stack_frames
2480 .into_iter()
2481 .filter(|frame| {
2482 // Workaround for JavaScript debug adapter sending out "fake" stack frames for delineating await points. This is fine,
2483 // except that they always use an id of 0 for it, which collides with other (valid) stack frames.
2484 !(frame.id == 0
2485 && frame.line == 0
2486 && frame.column == 0
2487 && frame.presentation_hint
2488 == Some(StackFramePresentationHint::Label))
2489 })
2490 .map(|frame| (frame.id, StackFrame::from(frame))),
2491 );
2492 }
2493
2494 this.invalidate_command_type::<ScopesCommand>();
2495 this.invalidate_command_type::<VariablesCommand>();
2496
2497 cx.emit(SessionEvent::StackTrace);
2498 },
2499 cx,
2500 );
2501 }
2502
2503 match self.session_state().threads.get(&thread_id) {
2504 Some(thread) => {
2505 if let Some(error) = &thread.stack_frames_error {
2506 Err(anyhow!(error.to_string()))
2507 } else {
2508 Ok(thread.stack_frames.clone())
2509 }
2510 }
2511 None => Ok(Vec::new()),
2512 }
2513 }
2514
2515 pub fn scopes(&mut self, stack_frame_id: u64, cx: &mut Context<Self>) -> &[dap::Scope] {
2516 if self.requests.contains_key(&TypeId::of::<ThreadsCommand>())
2517 && self
2518 .requests
2519 .contains_key(&TypeId::of::<StackTraceCommand>())
2520 {
2521 self.fetch(
2522 ScopesCommand { stack_frame_id },
2523 move |this, scopes, cx| {
2524 let Some(scopes) = scopes.log_err() else {
2525 return
2526 };
2527
2528 for scope in scopes.iter() {
2529 this.variables(scope.variables_reference, cx);
2530 }
2531
2532 let entry = this
2533 .active_snapshot
2534 .stack_frames
2535 .entry(stack_frame_id)
2536 .and_modify(|stack_frame| {
2537 stack_frame.scopes = scopes;
2538 });
2539
2540 cx.emit(SessionEvent::Variables);
2541
2542 debug_assert!(
2543 matches!(entry, indexmap::map::Entry::Occupied(_)),
2544 "Sent scopes request for stack_frame_id that doesn't exist or hasn't been fetched"
2545 );
2546 },
2547 cx,
2548 );
2549 }
2550
2551 self.session_state()
2552 .stack_frames
2553 .get(&stack_frame_id)
2554 .map(|frame| frame.scopes.as_slice())
2555 .unwrap_or_default()
2556 }
2557
2558 pub fn variables_by_stack_frame_id(
2559 &self,
2560 stack_frame_id: StackFrameId,
2561 globals: bool,
2562 locals: bool,
2563 ) -> Vec<dap::Variable> {
2564 let state = self.session_state();
2565 let Some(stack_frame) = state.stack_frames.get(&stack_frame_id) else {
2566 return Vec::new();
2567 };
2568
2569 stack_frame
2570 .scopes
2571 .iter()
2572 .filter(|scope| {
2573 (scope.name.to_lowercase().contains("local") && locals)
2574 || (scope.name.to_lowercase().contains("global") && globals)
2575 })
2576 .filter_map(|scope| state.variables.get(&scope.variables_reference))
2577 .flatten()
2578 .cloned()
2579 .collect()
2580 }
2581
2582 pub fn watchers(&self) -> &HashMap<SharedString, Watcher> {
2583 &self.watchers
2584 }
2585
2586 pub fn add_watcher(
2587 &mut self,
2588 expression: SharedString,
2589 frame_id: u64,
2590 cx: &mut Context<Self>,
2591 ) -> Task<Result<()>> {
2592 let request = self.state.request_dap(EvaluateCommand {
2593 expression: expression.to_string(),
2594 context: Some(EvaluateArgumentsContext::Watch),
2595 frame_id: Some(frame_id),
2596 source: None,
2597 });
2598
2599 cx.spawn(async move |this, cx| {
2600 let response = request.await?;
2601
2602 this.update(cx, |session, cx| {
2603 session.watchers.insert(
2604 expression.clone(),
2605 Watcher {
2606 expression,
2607 value: response.result.into(),
2608 variables_reference: response.variables_reference,
2609 presentation_hint: response.presentation_hint,
2610 },
2611 );
2612 cx.emit(SessionEvent::Watchers);
2613 })
2614 })
2615 }
2616
2617 pub fn refresh_watchers(&mut self, frame_id: u64, cx: &mut Context<Self>) {
2618 let watches = self.watchers.clone();
2619 for (_, watch) in watches.into_iter() {
2620 self.add_watcher(watch.expression.clone(), frame_id, cx)
2621 .detach();
2622 }
2623 }
2624
2625 pub fn remove_watcher(&mut self, expression: SharedString) {
2626 self.watchers.remove(&expression);
2627 }
2628
2629 pub fn variables(
2630 &mut self,
2631 variables_reference: VariableReference,
2632 cx: &mut Context<Self>,
2633 ) -> Vec<dap::Variable> {
2634 let command = VariablesCommand {
2635 variables_reference,
2636 filter: None,
2637 start: None,
2638 count: None,
2639 format: None,
2640 };
2641
2642 self.fetch(
2643 command,
2644 move |this, variables, cx| {
2645 let Some(variables) = variables.log_err() else {
2646 return;
2647 };
2648
2649 this.active_snapshot
2650 .variables
2651 .insert(variables_reference, variables);
2652
2653 cx.emit(SessionEvent::Variables);
2654 cx.emit(SessionEvent::InvalidateInlineValue);
2655 },
2656 cx,
2657 );
2658
2659 self.session_state()
2660 .variables
2661 .get(&variables_reference)
2662 .cloned()
2663 .unwrap_or_default()
2664 }
2665
2666 pub fn data_breakpoint_info(
2667 &mut self,
2668 context: Arc<DataBreakpointContext>,
2669 mode: Option<String>,
2670 cx: &mut Context<Self>,
2671 ) -> Task<Option<dap::DataBreakpointInfoResponse>> {
2672 let command = DataBreakpointInfoCommand { context, mode };
2673
2674 self.request(command, |_, response, _| response.ok(), cx)
2675 }
2676
2677 pub fn set_variable_value(
2678 &mut self,
2679 stack_frame_id: u64,
2680 variables_reference: u64,
2681 name: String,
2682 value: String,
2683 cx: &mut Context<Self>,
2684 ) {
2685 if self.capabilities.supports_set_variable.unwrap_or_default() {
2686 self.request(
2687 SetVariableValueCommand {
2688 name,
2689 value,
2690 variables_reference,
2691 },
2692 move |this, response, cx| {
2693 let response = response.log_err()?;
2694 this.invalidate_command_type::<VariablesCommand>();
2695 this.invalidate_command_type::<ReadMemory>();
2696 this.memory.clear(cx.background_executor());
2697 this.refresh_watchers(stack_frame_id, cx);
2698 cx.emit(SessionEvent::Variables);
2699 Some(response)
2700 },
2701 cx,
2702 )
2703 .detach();
2704 }
2705 }
2706
2707 pub fn evaluate(
2708 &mut self,
2709 expression: String,
2710 context: Option<EvaluateArgumentsContext>,
2711 frame_id: Option<u64>,
2712 source: Option<Source>,
2713 cx: &mut Context<Self>,
2714 ) -> Task<()> {
2715 let event = dap::OutputEvent {
2716 category: None,
2717 output: format!("> {expression}"),
2718 group: None,
2719 variables_reference: None,
2720 source: None,
2721 line: None,
2722 column: None,
2723 data: None,
2724 location_reference: None,
2725 };
2726 self.push_output(event);
2727 let request = self.state.request_dap(EvaluateCommand {
2728 expression,
2729 context,
2730 frame_id,
2731 source,
2732 });
2733 cx.spawn(async move |this, cx| {
2734 let response = request.await;
2735 this.update(cx, |this, cx| {
2736 this.memory.clear(cx.background_executor());
2737 this.invalidate_command_type::<ReadMemory>();
2738 this.invalidate_command_type::<VariablesCommand>();
2739 cx.emit(SessionEvent::Variables);
2740 match response {
2741 Ok(response) => {
2742 let event = dap::OutputEvent {
2743 category: None,
2744 output: format!("< {}", &response.result),
2745 group: None,
2746 variables_reference: Some(response.variables_reference),
2747 source: None,
2748 line: None,
2749 column: None,
2750 data: None,
2751 location_reference: None,
2752 };
2753 this.push_output(event);
2754 }
2755 Err(e) => {
2756 let event = dap::OutputEvent {
2757 category: None,
2758 output: format!("{}", e),
2759 group: None,
2760 variables_reference: None,
2761 source: None,
2762 line: None,
2763 column: None,
2764 data: None,
2765 location_reference: None,
2766 };
2767 this.push_output(event);
2768 }
2769 };
2770 cx.notify();
2771 })
2772 .ok();
2773 })
2774 }
2775
2776 pub fn location(
2777 &mut self,
2778 reference: u64,
2779 cx: &mut Context<Self>,
2780 ) -> Option<dap::LocationsResponse> {
2781 self.fetch(
2782 LocationsCommand { reference },
2783 move |this, response, _| {
2784 let Some(response) = response.log_err() else {
2785 return;
2786 };
2787 this.active_snapshot.locations.insert(reference, response);
2788 },
2789 cx,
2790 );
2791 self.session_state().locations.get(&reference).cloned()
2792 }
2793
2794 pub fn is_attached(&self) -> bool {
2795 let SessionState::Running(local_mode) = &self.state else {
2796 return false;
2797 };
2798 local_mode.binary.request_args.request == StartDebuggingRequestArgumentsRequest::Attach
2799 }
2800
2801 pub fn disconnect_client(&mut self, cx: &mut Context<Self>) {
2802 let command = DisconnectCommand {
2803 restart: Some(false),
2804 terminate_debuggee: Some(false),
2805 suspend_debuggee: Some(false),
2806 };
2807
2808 self.request(command, Self::empty_response, cx).detach()
2809 }
2810
2811 pub fn terminate_threads(&mut self, thread_ids: Option<Vec<ThreadId>>, cx: &mut Context<Self>) {
2812 if self
2813 .capabilities
2814 .supports_terminate_threads_request
2815 .unwrap_or_default()
2816 {
2817 self.request(
2818 TerminateThreadsCommand {
2819 thread_ids: thread_ids.map(|ids| ids.into_iter().map(|id| id.0).collect()),
2820 },
2821 Self::clear_active_debug_line_response,
2822 cx,
2823 )
2824 .detach();
2825 } else {
2826 self.shutdown(cx).detach();
2827 }
2828 }
2829
2830 pub fn thread_state(&self, thread_id: ThreadId) -> Option<ThreadStatus> {
2831 self.session_state().thread_states.thread_state(thread_id)
2832 }
2833
2834 pub fn quirks(&self) -> SessionQuirks {
2835 self.quirks
2836 }
2837
2838 fn launch_browser_for_remote_server(
2839 &mut self,
2840 mut request: LaunchBrowserInCompanionParams,
2841 cx: &mut Context<Self>,
2842 ) {
2843 let Some(remote_client) = self.remote_client.clone() else {
2844 log::error!("can't launch browser in companion for non-remote project");
2845 return;
2846 };
2847 let Some(http_client) = self.http_client.clone() else {
2848 return;
2849 };
2850 let Some(node_runtime) = self.node_runtime.clone() else {
2851 return;
2852 };
2853
2854 let mut console_output = self.console_output(cx);
2855 let task = cx.spawn(async move |this, cx| {
2856 let forward_ports_process = if remote_client
2857 .read_with(cx, |client, _| client.shares_network_interface())?
2858 {
2859 request.other.insert(
2860 "proxyUri".into(),
2861 format!("127.0.0.1:{}", request.server_port).into(),
2862 );
2863 None
2864 } else {
2865 let port = TcpTransport::unused_port(Ipv4Addr::LOCALHOST)
2866 .await
2867 .context("getting port for DAP")?;
2868 request
2869 .other
2870 .insert("proxyUri".into(), format!("127.0.0.1:{port}").into());
2871 let mut port_forwards = vec![(port, "localhost".to_owned(), request.server_port)];
2872
2873 if let Some(value) = request.params.get("url")
2874 && let Some(url) = value.as_str()
2875 && let Some(url) = Url::parse(url).ok()
2876 && let Some(frontend_port) = url.port()
2877 {
2878 port_forwards.push((frontend_port, "localhost".to_owned(), frontend_port));
2879 }
2880
2881 let child = remote_client.update(cx, |client, _| {
2882 let command = client.build_forward_ports_command(port_forwards)?;
2883 let child = new_smol_command(command.program)
2884 .args(command.args)
2885 .envs(command.env)
2886 .spawn()
2887 .context("spawning port forwarding process")?;
2888 anyhow::Ok(child)
2889 })??;
2890 Some(child)
2891 };
2892
2893 let mut companion_process = None;
2894 let companion_port =
2895 if let Some(companion_port) = this.read_with(cx, |this, _| this.companion_port)? {
2896 companion_port
2897 } else {
2898 let task = cx.spawn(async move |cx| spawn_companion(node_runtime, cx).await);
2899 match task.await {
2900 Ok((port, child)) => {
2901 companion_process = Some(child);
2902 port
2903 }
2904 Err(e) => {
2905 console_output
2906 .send(format!("Failed to launch browser companion process: {e}"))
2907 .await
2908 .ok();
2909 return Err(e);
2910 }
2911 }
2912 };
2913
2914 let mut background_tasks = Vec::new();
2915 if let Some(mut forward_ports_process) = forward_ports_process {
2916 background_tasks.push(cx.spawn(async move |_| {
2917 forward_ports_process.status().await.log_err();
2918 }));
2919 };
2920 if let Some(mut companion_process) = companion_process {
2921 if let Some(stderr) = companion_process.stderr.take() {
2922 let mut console_output = console_output.clone();
2923 background_tasks.push(cx.spawn(async move |_| {
2924 let mut stderr = BufReader::new(stderr);
2925 let mut line = String::new();
2926 while let Ok(n) = stderr.read_line(&mut line).await
2927 && n > 0
2928 {
2929 console_output
2930 .send(format!("companion stderr: {line}"))
2931 .await
2932 .ok();
2933 line.clear();
2934 }
2935 }));
2936 }
2937 background_tasks.push(cx.spawn({
2938 let mut console_output = console_output.clone();
2939 async move |_| match companion_process.status().await {
2940 Ok(status) => {
2941 if status.success() {
2942 console_output
2943 .send("Companion process exited normally".into())
2944 .await
2945 .ok();
2946 } else {
2947 console_output
2948 .send(format!(
2949 "Companion process exited abnormally with {status:?}"
2950 ))
2951 .await
2952 .ok();
2953 }
2954 }
2955 Err(e) => {
2956 console_output
2957 .send(format!("Failed to join companion process: {e}"))
2958 .await
2959 .ok();
2960 }
2961 }
2962 }));
2963 }
2964
2965 // TODO pass wslInfo as needed
2966
2967 let companion_address = format!("127.0.0.1:{companion_port}");
2968 let mut companion_started = false;
2969 for _ in 0..10 {
2970 if TcpStream::connect(&companion_address).await.is_ok() {
2971 companion_started = true;
2972 break;
2973 }
2974 cx.background_executor()
2975 .timer(Duration::from_millis(100))
2976 .await;
2977 }
2978 if !companion_started {
2979 console_output
2980 .send("Browser companion failed to start".into())
2981 .await
2982 .ok();
2983 bail!("Browser companion failed to start");
2984 }
2985
2986 let response = http_client
2987 .post_json(
2988 &format!("http://{companion_address}/launch-and-attach"),
2989 serde_json::to_string(&request)
2990 .context("serializing request")?
2991 .into(),
2992 )
2993 .await;
2994 match response {
2995 Ok(response) => {
2996 if !response.status().is_success() {
2997 console_output
2998 .send("Launch request to companion failed".into())
2999 .await
3000 .ok();
3001 return Err(anyhow!("launch request failed"));
3002 }
3003 }
3004 Err(e) => {
3005 console_output
3006 .send("Failed to read response from companion".into())
3007 .await
3008 .ok();
3009 return Err(e);
3010 }
3011 }
3012
3013 this.update(cx, |this, _| {
3014 this.background_tasks.extend(background_tasks);
3015 this.companion_port = Some(companion_port);
3016 })?;
3017
3018 anyhow::Ok(())
3019 });
3020 self.background_tasks.push(cx.spawn(async move |_, _| {
3021 task.await.log_err();
3022 }));
3023 }
3024
3025 fn kill_browser(&self, request: KillCompanionBrowserParams, cx: &mut App) {
3026 let Some(companion_port) = self.companion_port else {
3027 log::error!("received killCompanionBrowser but js-debug-companion is not running");
3028 return;
3029 };
3030 let Some(http_client) = self.http_client.clone() else {
3031 return;
3032 };
3033
3034 cx.spawn(async move |_| {
3035 http_client
3036 .post_json(
3037 &format!("http://127.0.0.1:{companion_port}/kill"),
3038 serde_json::to_string(&request)
3039 .context("serializing request")?
3040 .into(),
3041 )
3042 .await?;
3043 anyhow::Ok(())
3044 })
3045 .detach_and_log_err(cx)
3046 }
3047}
3048
3049#[derive(Serialize, Deserialize, Debug)]
3050#[serde(rename_all = "camelCase")]
3051struct LaunchBrowserInCompanionParams {
3052 server_port: u16,
3053 params: HashMap<String, serde_json::Value>,
3054 #[serde(flatten)]
3055 other: HashMap<String, serde_json::Value>,
3056}
3057
3058#[derive(Serialize, Deserialize, Debug)]
3059#[serde(rename_all = "camelCase")]
3060struct KillCompanionBrowserParams {
3061 launch_id: u64,
3062}
3063
3064async fn spawn_companion(
3065 node_runtime: NodeRuntime,
3066 cx: &mut AsyncApp,
3067) -> Result<(u16, smol::process::Child)> {
3068 let binary_path = node_runtime
3069 .binary_path()
3070 .await
3071 .context("getting node path")?;
3072 let path = cx
3073 .spawn(async move |cx| get_or_install_companion(node_runtime, cx).await)
3074 .await?;
3075 log::info!("will launch js-debug-companion version {path:?}");
3076
3077 let port = {
3078 let listener = TcpListener::bind("127.0.0.1:0")
3079 .await
3080 .context("getting port for companion")?;
3081 listener.local_addr()?.port()
3082 };
3083
3084 let dir = paths::data_dir()
3085 .join("js_debug_companion_state")
3086 .to_string_lossy()
3087 .to_string();
3088
3089 let child = new_smol_command(binary_path)
3090 .arg(path)
3091 .args([
3092 format!("--listen=127.0.0.1:{port}"),
3093 format!("--state={dir}"),
3094 ])
3095 .stdin(Stdio::piped())
3096 .stdout(Stdio::piped())
3097 .stderr(Stdio::piped())
3098 .spawn()
3099 .context("spawning companion child process")?;
3100
3101 Ok((port, child))
3102}
3103
3104async fn get_or_install_companion(node: NodeRuntime, cx: &mut AsyncApp) -> Result<PathBuf> {
3105 const PACKAGE_NAME: &str = "@zed-industries/js-debug-companion-cli";
3106
3107 async fn install_latest_version(dir: PathBuf, node: NodeRuntime) -> Result<PathBuf> {
3108 let temp_dir = tempfile::tempdir().context("creating temporary directory")?;
3109 node.npm_install_packages(temp_dir.path(), &[(PACKAGE_NAME, "latest")])
3110 .await
3111 .context("installing latest companion package")?;
3112 let version = node
3113 .npm_package_installed_version(temp_dir.path(), PACKAGE_NAME)
3114 .await
3115 .context("getting installed companion version")?
3116 .context("companion was not installed")?;
3117 smol::fs::rename(temp_dir.path(), dir.join(&version))
3118 .await
3119 .context("moving companion package into place")?;
3120 Ok(dir.join(version))
3121 }
3122
3123 let dir = paths::debug_adapters_dir().join("js-debug-companion");
3124 let (latest_installed_version, latest_version) = cx
3125 .background_spawn({
3126 let dir = dir.clone();
3127 let node = node.clone();
3128 async move {
3129 smol::fs::create_dir_all(&dir)
3130 .await
3131 .context("creating companion installation directory")?;
3132
3133 let mut children = smol::fs::read_dir(&dir)
3134 .await
3135 .context("reading companion installation directory")?
3136 .try_collect::<Vec<_>>()
3137 .await
3138 .context("reading companion installation directory entries")?;
3139 children
3140 .sort_by_key(|child| semver::Version::parse(child.file_name().to_str()?).ok());
3141
3142 let latest_installed_version = children.last().and_then(|child| {
3143 let version = child.file_name().into_string().ok()?;
3144 Some((child.path(), version))
3145 });
3146 let latest_version = node
3147 .npm_package_latest_version(PACKAGE_NAME)
3148 .await
3149 .log_err();
3150 anyhow::Ok((latest_installed_version, latest_version))
3151 }
3152 })
3153 .await?;
3154
3155 let path = if let Some((installed_path, installed_version)) = latest_installed_version {
3156 if let Some(latest_version) = latest_version
3157 && latest_version != installed_version
3158 {
3159 cx.background_spawn(install_latest_version(dir.clone(), node.clone()))
3160 .detach();
3161 }
3162 Ok(installed_path)
3163 } else {
3164 cx.background_spawn(install_latest_version(dir.clone(), node.clone()))
3165 .await
3166 };
3167
3168 Ok(path?
3169 .join("node_modules")
3170 .join(PACKAGE_NAME)
3171 .join("out")
3172 .join("cli.js"))
3173}