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 historic_snapshots(&self) -> &VecDeque<SessionSnapshot> {
1440 &self.snapshots
1441 }
1442
1443 pub fn select_historic_snapshot(&mut self, ix: Option<usize>, cx: &mut Context<Session>) {
1444 if self.selected_snapshot_index == ix {
1445 return;
1446 }
1447
1448 if self
1449 .selected_snapshot_index
1450 .is_some_and(|ix| self.snapshots.len() <= ix)
1451 {
1452 debug_panic!("Attempted to select a debug session with an out of bounds index");
1453 return;
1454 }
1455
1456 self.selected_snapshot_index = ix;
1457 cx.emit(SessionEvent::HistoricSnapshotSelected);
1458 cx.notify();
1459 }
1460
1461 pub fn active_snapshot_index(&self) -> Option<usize> {
1462 self.selected_snapshot_index
1463 }
1464
1465 fn handle_stopped_event(&mut self, event: StoppedEvent, cx: &mut Context<Self>) {
1466 self.push_to_history();
1467
1468 self.state.stopped();
1469 // todo(debugger): Find a clean way to get around the clone
1470 let breakpoint_store = self.breakpoint_store.clone();
1471 if let Some((local, path)) = self.as_running_mut().and_then(|local| {
1472 let breakpoint = local.tmp_breakpoint.take()?;
1473 let path = breakpoint.path;
1474 Some((local, path))
1475 }) {
1476 local
1477 .send_breakpoints_from_path(
1478 path,
1479 BreakpointUpdatedReason::Toggled,
1480 &breakpoint_store,
1481 cx,
1482 )
1483 .detach();
1484 };
1485
1486 if event.all_threads_stopped.unwrap_or_default() || event.thread_id.is_none() {
1487 self.active_snapshot.thread_states.stop_all_threads();
1488 self.invalidate_command_type::<StackTraceCommand>();
1489 }
1490
1491 // Event if we stopped all threads we still need to insert the thread_id
1492 // to our own data
1493 if let Some(thread_id) = event.thread_id {
1494 self.active_snapshot
1495 .thread_states
1496 .stop_thread(ThreadId(thread_id));
1497
1498 self.invalidate_state(
1499 &StackTraceCommand {
1500 thread_id,
1501 start_frame: None,
1502 levels: None,
1503 }
1504 .into(),
1505 );
1506 }
1507
1508 self.invalidate_generic();
1509 self.active_snapshot.threads.clear();
1510 self.active_snapshot.variables.clear();
1511 cx.emit(SessionEvent::Stopped(
1512 event
1513 .thread_id
1514 .map(Into::into)
1515 .filter(|_| !event.preserve_focus_hint.unwrap_or(false)),
1516 ));
1517 cx.emit(SessionEvent::InvalidateInlineValue);
1518 cx.notify();
1519 }
1520
1521 pub(crate) fn handle_dap_event(&mut self, event: Box<Events>, cx: &mut Context<Self>) {
1522 match *event {
1523 Events::Initialized(_) => {
1524 debug_assert!(
1525 false,
1526 "Initialized event should have been handled in LocalMode"
1527 );
1528 }
1529 Events::Stopped(event) => self.handle_stopped_event(event, cx),
1530 Events::Continued(event) => {
1531 if event.all_threads_continued.unwrap_or_default() {
1532 self.active_snapshot.thread_states.continue_all_threads();
1533 self.breakpoint_store.update(cx, |store, cx| {
1534 store.remove_active_position(Some(self.session_id()), cx)
1535 });
1536 } else {
1537 self.active_snapshot
1538 .thread_states
1539 .continue_thread(ThreadId(event.thread_id));
1540 }
1541 // todo(debugger): We should be able to get away with only invalidating generic if all threads were continued
1542 self.invalidate_generic();
1543 }
1544 Events::Exited(_event) => {
1545 self.clear_active_debug_line(cx);
1546 }
1547 Events::Terminated(_) => {
1548 self.shutdown(cx).detach();
1549 }
1550 Events::Thread(event) => {
1551 let thread_id = ThreadId(event.thread_id);
1552
1553 match event.reason {
1554 dap::ThreadEventReason::Started => {
1555 self.active_snapshot
1556 .thread_states
1557 .continue_thread(thread_id);
1558 }
1559 dap::ThreadEventReason::Exited => {
1560 self.active_snapshot.thread_states.exit_thread(thread_id);
1561 }
1562 reason => {
1563 log::error!("Unhandled thread event reason {:?}", reason);
1564 }
1565 }
1566 self.invalidate_state(&ThreadsCommand.into());
1567 cx.notify();
1568 }
1569 Events::Output(event) => {
1570 if event
1571 .category
1572 .as_ref()
1573 .is_some_and(|category| *category == OutputEventCategory::Telemetry)
1574 {
1575 return;
1576 }
1577
1578 self.push_output(event);
1579 cx.notify();
1580 }
1581 Events::Breakpoint(event) => self.breakpoint_store.update(cx, |store, _| {
1582 store.update_session_breakpoint(self.session_id(), event.reason, event.breakpoint);
1583 }),
1584 Events::Module(event) => {
1585 match event.reason {
1586 dap::ModuleEventReason::New => {
1587 self.active_snapshot.modules.push(event.module);
1588 }
1589 dap::ModuleEventReason::Changed => {
1590 if let Some(module) = self
1591 .active_snapshot
1592 .modules
1593 .iter_mut()
1594 .find(|other| event.module.id == other.id)
1595 {
1596 *module = event.module;
1597 }
1598 }
1599 dap::ModuleEventReason::Removed => {
1600 self.active_snapshot
1601 .modules
1602 .retain(|other| event.module.id != other.id);
1603 }
1604 }
1605
1606 // todo(debugger): We should only send the invalidate command to downstream clients.
1607 // self.invalidate_state(&ModulesCommand.into());
1608 }
1609 Events::LoadedSource(_) => {
1610 self.invalidate_state(&LoadedSourcesCommand.into());
1611 }
1612 Events::Capabilities(event) => {
1613 self.capabilities = self.capabilities.merge(event.capabilities);
1614
1615 // The adapter might've enabled new exception breakpoints (or disabled existing ones).
1616 let recent_filters = self
1617 .capabilities
1618 .exception_breakpoint_filters
1619 .iter()
1620 .flatten()
1621 .map(|filter| (filter.filter.clone(), filter.clone()))
1622 .collect::<BTreeMap<_, _>>();
1623 for filter in recent_filters.values() {
1624 let default = filter.default.unwrap_or_default();
1625 self.exception_breakpoints
1626 .entry(filter.filter.clone())
1627 .or_insert_with(|| (filter.clone(), default));
1628 }
1629 self.exception_breakpoints
1630 .retain(|k, _| recent_filters.contains_key(k));
1631 if self.is_started() {
1632 self.send_exception_breakpoints(cx);
1633 }
1634
1635 // Remove the ones that no longer exist.
1636 cx.notify();
1637 }
1638 Events::Memory(_) => {}
1639 Events::Process(_) => {}
1640 Events::ProgressEnd(_) => {}
1641 Events::ProgressStart(_) => {}
1642 Events::ProgressUpdate(_) => {}
1643 Events::Invalidated(_) => {}
1644 Events::Other(event) => {
1645 if event.event == "launchBrowserInCompanion" {
1646 let Some(request) = serde_json::from_value(event.body).ok() else {
1647 log::error!("failed to deserialize launchBrowserInCompanion event");
1648 return;
1649 };
1650 self.launch_browser_for_remote_server(request, cx);
1651 } else if event.event == "killCompanionBrowser" {
1652 let Some(request) = serde_json::from_value(event.body).ok() else {
1653 log::error!("failed to deserialize killCompanionBrowser event");
1654 return;
1655 };
1656 self.kill_browser(request, cx);
1657 }
1658 }
1659 }
1660 }
1661
1662 /// 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.
1663 fn fetch<T: LocalDapCommand + PartialEq + Eq + Hash>(
1664 &mut self,
1665 request: T,
1666 process_result: impl FnOnce(&mut Self, Result<T::Response>, &mut Context<Self>) + 'static,
1667 cx: &mut Context<Self>,
1668 ) {
1669 const {
1670 assert!(
1671 T::CACHEABLE,
1672 "Only requests marked as cacheable should invoke `fetch`"
1673 );
1674 }
1675
1676 if (!self.active_snapshot.thread_states.any_stopped_thread()
1677 && request.type_id() != TypeId::of::<ThreadsCommand>())
1678 || self.selected_snapshot_index.is_some()
1679 || self.is_session_terminated
1680 {
1681 return;
1682 }
1683
1684 let request_map = self
1685 .requests
1686 .entry(std::any::TypeId::of::<T>())
1687 .or_default();
1688
1689 if let Entry::Vacant(vacant) = request_map.entry(request.into()) {
1690 let command = vacant.key().0.clone().as_any_arc().downcast::<T>().unwrap();
1691
1692 let task = Self::request_inner::<Arc<T>>(
1693 &self.capabilities,
1694 &self.state,
1695 command,
1696 |this, result, cx| {
1697 process_result(this, result, cx);
1698 None
1699 },
1700 cx,
1701 );
1702 let task = cx
1703 .background_executor()
1704 .spawn(async move {
1705 let _ = task.await?;
1706 Some(())
1707 })
1708 .shared();
1709
1710 vacant.insert(task);
1711 cx.notify();
1712 }
1713 }
1714
1715 fn request_inner<T: LocalDapCommand + PartialEq + Eq + Hash>(
1716 capabilities: &Capabilities,
1717 mode: &SessionState,
1718 request: T,
1719 process_result: impl FnOnce(
1720 &mut Self,
1721 Result<T::Response>,
1722 &mut Context<Self>,
1723 ) -> Option<T::Response>
1724 + 'static,
1725 cx: &mut Context<Self>,
1726 ) -> Task<Option<T::Response>> {
1727 if !T::is_supported(capabilities) {
1728 log::warn!(
1729 "Attempted to send a DAP request that isn't supported: {:?}",
1730 request
1731 );
1732 let error = Err(anyhow::Error::msg(
1733 "Couldn't complete request because it's not supported",
1734 ));
1735 return cx.spawn(async move |this, cx| {
1736 this.update(cx, |this, cx| process_result(this, error, cx))
1737 .ok()
1738 .flatten()
1739 });
1740 }
1741
1742 let request = mode.request_dap(request);
1743 cx.spawn(async move |this, cx| {
1744 let result = request.await;
1745 this.update(cx, |this, cx| process_result(this, result, cx))
1746 .ok()
1747 .flatten()
1748 })
1749 }
1750
1751 fn request<T: LocalDapCommand + PartialEq + Eq + Hash>(
1752 &self,
1753 request: T,
1754 process_result: impl FnOnce(
1755 &mut Self,
1756 Result<T::Response>,
1757 &mut Context<Self>,
1758 ) -> Option<T::Response>
1759 + 'static,
1760 cx: &mut Context<Self>,
1761 ) -> Task<Option<T::Response>> {
1762 Self::request_inner(&self.capabilities, &self.state, request, process_result, cx)
1763 }
1764
1765 fn invalidate_command_type<Command: LocalDapCommand>(&mut self) {
1766 self.requests.remove(&std::any::TypeId::of::<Command>());
1767 }
1768
1769 fn invalidate_generic(&mut self) {
1770 self.invalidate_command_type::<ModulesCommand>();
1771 self.invalidate_command_type::<LoadedSourcesCommand>();
1772 self.invalidate_command_type::<ThreadsCommand>();
1773 self.invalidate_command_type::<DataBreakpointInfoCommand>();
1774 self.invalidate_command_type::<ReadMemory>();
1775 let executor = self.as_running().map(|running| running.executor.clone());
1776 if let Some(executor) = executor {
1777 self.memory.clear(&executor);
1778 }
1779 }
1780
1781 fn invalidate_state(&mut self, key: &RequestSlot) {
1782 self.requests
1783 .entry((&*key.0 as &dyn Any).type_id())
1784 .and_modify(|request_map| {
1785 request_map.remove(key);
1786 });
1787 }
1788
1789 fn push_output(&mut self, event: OutputEvent) {
1790 self.output.push_back(event);
1791 self.output_token.0 += 1;
1792 }
1793
1794 pub fn any_stopped_thread(&self) -> bool {
1795 self.active_snapshot.thread_states.any_stopped_thread()
1796 }
1797
1798 pub fn thread_status(&self, thread_id: ThreadId) -> ThreadStatus {
1799 self.active_snapshot.thread_states.thread_status(thread_id)
1800 }
1801
1802 pub fn threads(&mut self, cx: &mut Context<Self>) -> Vec<(dap::Thread, ThreadStatus)> {
1803 self.fetch(
1804 dap_command::ThreadsCommand,
1805 |this, result, cx| {
1806 let Some(result) = result.log_err() else {
1807 return;
1808 };
1809
1810 this.active_snapshot.threads = result
1811 .into_iter()
1812 .map(|thread| (ThreadId(thread.id), Thread::from(thread)))
1813 .collect();
1814
1815 this.invalidate_command_type::<StackTraceCommand>();
1816 cx.emit(SessionEvent::Threads);
1817 cx.notify();
1818 },
1819 cx,
1820 );
1821
1822 let state = self.session_state();
1823 state
1824 .threads
1825 .values()
1826 .map(|thread| {
1827 (
1828 thread.dap.clone(),
1829 state.thread_states.thread_status(ThreadId(thread.dap.id)),
1830 )
1831 })
1832 .collect()
1833 }
1834
1835 pub fn modules(&mut self, cx: &mut Context<Self>) -> &[Module] {
1836 self.fetch(
1837 dap_command::ModulesCommand,
1838 |this, result, cx| {
1839 let Some(result) = result.log_err() else {
1840 return;
1841 };
1842
1843 this.active_snapshot.modules = result;
1844 cx.emit(SessionEvent::Modules);
1845 cx.notify();
1846 },
1847 cx,
1848 );
1849
1850 &self.session_state().modules
1851 }
1852
1853 // CodeLLDB returns the size of a pointed-to-memory, which we can use to make the experience of go-to-memory better.
1854 pub fn data_access_size(
1855 &mut self,
1856 frame_id: Option<u64>,
1857 evaluate_name: &str,
1858 cx: &mut Context<Self>,
1859 ) -> Task<Option<u64>> {
1860 let request = self.request(
1861 EvaluateCommand {
1862 expression: format!("?${{sizeof({evaluate_name})}}"),
1863 frame_id,
1864
1865 context: Some(EvaluateArgumentsContext::Repl),
1866 source: None,
1867 },
1868 |_, response, _| response.ok(),
1869 cx,
1870 );
1871 cx.background_spawn(async move {
1872 let result = request.await?;
1873 result.result.parse().ok()
1874 })
1875 }
1876
1877 pub fn memory_reference_of_expr(
1878 &mut self,
1879 frame_id: Option<u64>,
1880 expression: String,
1881 cx: &mut Context<Self>,
1882 ) -> Task<Option<(String, Option<String>)>> {
1883 let request = self.request(
1884 EvaluateCommand {
1885 expression,
1886 frame_id,
1887
1888 context: Some(EvaluateArgumentsContext::Repl),
1889 source: None,
1890 },
1891 |_, response, _| response.ok(),
1892 cx,
1893 );
1894 cx.background_spawn(async move {
1895 let result = request.await?;
1896 result
1897 .memory_reference
1898 .map(|reference| (reference, result.type_))
1899 })
1900 }
1901
1902 pub fn write_memory(&mut self, address: u64, data: &[u8], cx: &mut Context<Self>) {
1903 let data = base64::engine::general_purpose::STANDARD.encode(data);
1904 self.request(
1905 WriteMemoryArguments {
1906 memory_reference: address.to_string(),
1907 data,
1908 allow_partial: None,
1909 offset: None,
1910 },
1911 |this, response, cx| {
1912 this.memory.clear(cx.background_executor());
1913 this.invalidate_command_type::<ReadMemory>();
1914 this.invalidate_command_type::<VariablesCommand>();
1915 cx.emit(SessionEvent::Variables);
1916 response.ok()
1917 },
1918 cx,
1919 )
1920 .detach();
1921 }
1922 pub fn read_memory(
1923 &mut self,
1924 range: RangeInclusive<u64>,
1925 cx: &mut Context<Self>,
1926 ) -> MemoryIterator {
1927 // This function is a bit more involved when it comes to fetching data.
1928 // Since we attempt to read memory in pages, we need to account for some parts
1929 // of memory being unreadable. Therefore, we start off by fetching a page per request.
1930 // In case that fails, we try to re-fetch smaller regions until we have the full range.
1931 let page_range = Memory::memory_range_to_page_range(range.clone());
1932 for page_address in PageAddress::iter_range(page_range) {
1933 self.read_single_page_memory(page_address, cx);
1934 }
1935 self.memory.memory_range(range)
1936 }
1937
1938 fn read_single_page_memory(&mut self, page_start: PageAddress, cx: &mut Context<Self>) {
1939 _ = maybe!({
1940 let builder = self.memory.build_page(page_start)?;
1941
1942 self.memory_read_fetch_page_recursive(builder, cx);
1943 Some(())
1944 });
1945 }
1946 fn memory_read_fetch_page_recursive(
1947 &mut self,
1948 mut builder: MemoryPageBuilder,
1949 cx: &mut Context<Self>,
1950 ) {
1951 let Some(next_request) = builder.next_request() else {
1952 // We're done fetching. Let's grab the page and insert it into our memory store.
1953 let (address, contents) = builder.build();
1954 self.memory.insert_page(address, contents);
1955
1956 return;
1957 };
1958 let size = next_request.size;
1959 self.fetch(
1960 ReadMemory {
1961 memory_reference: format!("0x{:X}", next_request.address),
1962 offset: Some(0),
1963 count: next_request.size,
1964 },
1965 move |this, memory, cx| {
1966 if let Ok(memory) = memory {
1967 builder.known(memory.content);
1968 if let Some(unknown) = memory.unreadable_bytes {
1969 builder.unknown(unknown);
1970 }
1971 // This is the recursive bit: if we're not yet done with
1972 // the whole page, we'll kick off a new request with smaller range.
1973 // Note that this function is recursive only conceptually;
1974 // since it kicks off a new request with callback, we don't need to worry about stack overflow.
1975 this.memory_read_fetch_page_recursive(builder, cx);
1976 } else {
1977 builder.unknown(size);
1978 }
1979 },
1980 cx,
1981 );
1982 }
1983
1984 pub fn ignore_breakpoints(&self) -> bool {
1985 self.ignore_breakpoints
1986 }
1987
1988 pub fn toggle_ignore_breakpoints(
1989 &mut self,
1990 cx: &mut App,
1991 ) -> Task<HashMap<Arc<Path>, anyhow::Error>> {
1992 self.set_ignore_breakpoints(!self.ignore_breakpoints, cx)
1993 }
1994
1995 pub(crate) fn set_ignore_breakpoints(
1996 &mut self,
1997 ignore: bool,
1998 cx: &mut App,
1999 ) -> Task<HashMap<Arc<Path>, anyhow::Error>> {
2000 if self.ignore_breakpoints == ignore {
2001 return Task::ready(HashMap::default());
2002 }
2003
2004 self.ignore_breakpoints = ignore;
2005
2006 if let Some(local) = self.as_running() {
2007 local.send_source_breakpoints(ignore, &self.breakpoint_store, cx)
2008 } else {
2009 // todo(debugger): We need to propagate this change to downstream sessions and send a message to upstream sessions
2010 unimplemented!()
2011 }
2012 }
2013
2014 pub fn data_breakpoints(&self) -> impl Iterator<Item = &DataBreakpointState> {
2015 self.data_breakpoints.values()
2016 }
2017
2018 pub fn exception_breakpoints(
2019 &self,
2020 ) -> impl Iterator<Item = &(ExceptionBreakpointsFilter, IsEnabled)> {
2021 self.exception_breakpoints.values()
2022 }
2023
2024 pub fn toggle_exception_breakpoint(&mut self, id: &str, cx: &App) {
2025 if let Some((_, is_enabled)) = self.exception_breakpoints.get_mut(id) {
2026 *is_enabled = !*is_enabled;
2027 self.send_exception_breakpoints(cx);
2028 }
2029 }
2030
2031 fn send_exception_breakpoints(&mut self, cx: &App) {
2032 if let Some(local) = self.as_running() {
2033 let exception_filters = self
2034 .exception_breakpoints
2035 .values()
2036 .filter_map(|(filter, is_enabled)| is_enabled.then(|| filter.clone()))
2037 .collect();
2038
2039 let supports_exception_filters = self
2040 .capabilities
2041 .supports_exception_filter_options
2042 .unwrap_or_default();
2043 local
2044 .send_exception_breakpoints(exception_filters, supports_exception_filters)
2045 .detach_and_log_err(cx);
2046 } else {
2047 debug_assert!(false, "Not implemented");
2048 }
2049 }
2050
2051 pub fn toggle_data_breakpoint(&mut self, id: &str, cx: &mut Context<'_, Session>) {
2052 if let Some(state) = self.data_breakpoints.get_mut(id) {
2053 state.is_enabled = !state.is_enabled;
2054 self.send_exception_breakpoints(cx);
2055 }
2056 }
2057
2058 fn send_data_breakpoints(&mut self, cx: &mut Context<Self>) {
2059 if let Some(mode) = self.as_running() {
2060 let breakpoints = self
2061 .data_breakpoints
2062 .values()
2063 .filter_map(|state| state.is_enabled.then(|| state.dap.clone()))
2064 .collect();
2065 let command = SetDataBreakpointsCommand { breakpoints };
2066 mode.request(command).detach_and_log_err(cx);
2067 }
2068 }
2069
2070 pub fn create_data_breakpoint(
2071 &mut self,
2072 context: Arc<DataBreakpointContext>,
2073 data_id: String,
2074 dap: dap::DataBreakpoint,
2075 cx: &mut Context<Self>,
2076 ) {
2077 if self.data_breakpoints.remove(&data_id).is_none() {
2078 self.data_breakpoints.insert(
2079 data_id,
2080 DataBreakpointState {
2081 dap,
2082 is_enabled: true,
2083 context,
2084 },
2085 );
2086 }
2087 self.send_data_breakpoints(cx);
2088 }
2089
2090 pub fn breakpoints_enabled(&self) -> bool {
2091 self.ignore_breakpoints
2092 }
2093
2094 pub fn loaded_sources(&mut self, cx: &mut Context<Self>) -> &[Source] {
2095 self.fetch(
2096 dap_command::LoadedSourcesCommand,
2097 |this, result, cx| {
2098 let Some(result) = result.log_err() else {
2099 return;
2100 };
2101 this.active_snapshot.loaded_sources = result;
2102 cx.emit(SessionEvent::LoadedSources);
2103 cx.notify();
2104 },
2105 cx,
2106 );
2107 &self.session_state().loaded_sources
2108 }
2109
2110 fn fallback_to_manual_restart(
2111 &mut self,
2112 res: Result<()>,
2113 cx: &mut Context<Self>,
2114 ) -> Option<()> {
2115 if res.log_err().is_none() {
2116 cx.emit(SessionStateEvent::Restart);
2117 return None;
2118 }
2119 Some(())
2120 }
2121
2122 fn empty_response(&mut self, res: Result<()>, _cx: &mut Context<Self>) -> Option<()> {
2123 res.log_err()?;
2124 Some(())
2125 }
2126
2127 fn on_step_response<T: LocalDapCommand + PartialEq + Eq + Hash>(
2128 thread_id: ThreadId,
2129 ) -> impl FnOnce(&mut Self, Result<T::Response>, &mut Context<Self>) -> Option<T::Response> + 'static
2130 {
2131 move |this, response, cx| match response.log_err() {
2132 Some(response) => {
2133 this.breakpoint_store.update(cx, |store, cx| {
2134 store.remove_active_position(Some(this.session_id()), cx)
2135 });
2136 Some(response)
2137 }
2138 None => {
2139 this.active_snapshot.thread_states.stop_thread(thread_id);
2140 cx.notify();
2141 None
2142 }
2143 }
2144 }
2145
2146 fn clear_active_debug_line_response(
2147 &mut self,
2148 response: Result<()>,
2149 cx: &mut Context<Session>,
2150 ) -> Option<()> {
2151 response.log_err()?;
2152 self.clear_active_debug_line(cx);
2153 Some(())
2154 }
2155
2156 fn clear_active_debug_line(&mut self, cx: &mut Context<Session>) {
2157 self.breakpoint_store.update(cx, |store, cx| {
2158 store.remove_active_position(Some(self.id), cx)
2159 });
2160 }
2161
2162 pub fn pause_thread(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) {
2163 self.request(
2164 PauseCommand {
2165 thread_id: thread_id.0,
2166 },
2167 Self::empty_response,
2168 cx,
2169 )
2170 .detach();
2171 }
2172
2173 pub fn restart_stack_frame(&mut self, stack_frame_id: u64, cx: &mut Context<Self>) {
2174 self.request(
2175 RestartStackFrameCommand { stack_frame_id },
2176 Self::empty_response,
2177 cx,
2178 )
2179 .detach();
2180 }
2181
2182 pub fn restart(&mut self, args: Option<Value>, cx: &mut Context<Self>) {
2183 if self.restart_task.is_some() || self.as_running().is_none() {
2184 return;
2185 }
2186
2187 let supports_dap_restart =
2188 self.capabilities.supports_restart_request.unwrap_or(false) && !self.is_terminated();
2189
2190 self.restart_task = Some(cx.spawn(async move |this, cx| {
2191 let _ = this.update(cx, |session, cx| {
2192 if supports_dap_restart {
2193 session
2194 .request(
2195 RestartCommand {
2196 raw: args.unwrap_or(Value::Null),
2197 },
2198 Self::fallback_to_manual_restart,
2199 cx,
2200 )
2201 .detach();
2202 } else {
2203 cx.emit(SessionStateEvent::Restart);
2204 }
2205 });
2206 }));
2207 }
2208
2209 pub fn shutdown(&mut self, cx: &mut Context<Self>) -> Task<()> {
2210 if self.is_session_terminated {
2211 return Task::ready(());
2212 }
2213
2214 self.is_session_terminated = true;
2215 self.active_snapshot.thread_states.exit_all_threads();
2216 cx.notify();
2217
2218 let task = match &mut self.state {
2219 SessionState::Running(_) => {
2220 if self
2221 .capabilities
2222 .supports_terminate_request
2223 .unwrap_or_default()
2224 {
2225 self.request(
2226 TerminateCommand {
2227 restart: Some(false),
2228 },
2229 Self::clear_active_debug_line_response,
2230 cx,
2231 )
2232 } else {
2233 self.request(
2234 DisconnectCommand {
2235 restart: Some(false),
2236 terminate_debuggee: Some(true),
2237 suspend_debuggee: Some(false),
2238 },
2239 Self::clear_active_debug_line_response,
2240 cx,
2241 )
2242 }
2243 }
2244 SessionState::Booting(build_task) => {
2245 build_task.take();
2246 Task::ready(Some(()))
2247 }
2248 };
2249
2250 cx.emit(SessionStateEvent::Shutdown);
2251
2252 cx.spawn(async move |this, cx| {
2253 task.await;
2254 let _ = this.update(cx, |this, _| {
2255 if let Some(adapter_client) = this.adapter_client() {
2256 adapter_client.kill();
2257 }
2258 });
2259 })
2260 }
2261
2262 pub fn completions(
2263 &mut self,
2264 query: CompletionsQuery,
2265 cx: &mut Context<Self>,
2266 ) -> Task<Result<Vec<dap::CompletionItem>>> {
2267 let task = self.request(query, |_, result, _| result.log_err(), cx);
2268
2269 cx.background_executor().spawn(async move {
2270 anyhow::Ok(
2271 task.await
2272 .map(|response| response.targets)
2273 .context("failed to fetch completions")?,
2274 )
2275 })
2276 }
2277
2278 pub fn continue_thread(&mut self, thread_id: ThreadId, cx: &mut Context<Self>) {
2279 self.select_historic_snapshot(None, cx);
2280
2281 let supports_single_thread_execution_requests =
2282 self.capabilities.supports_single_thread_execution_requests;
2283 self.active_snapshot
2284 .thread_states
2285 .continue_thread(thread_id);
2286 self.request(
2287 ContinueCommand {
2288 args: ContinueArguments {
2289 thread_id: thread_id.0,
2290 single_thread: supports_single_thread_execution_requests,
2291 },
2292 },
2293 Self::on_step_response::<ContinueCommand>(thread_id),
2294 cx,
2295 )
2296 .detach();
2297 }
2298
2299 pub fn adapter_client(&self) -> Option<Arc<DebugAdapterClient>> {
2300 match self.state {
2301 SessionState::Running(ref local) => Some(local.client.clone()),
2302 SessionState::Booting(_) => None,
2303 }
2304 }
2305
2306 pub fn has_ever_stopped(&self) -> bool {
2307 self.state.has_ever_stopped()
2308 }
2309
2310 pub fn step_over(
2311 &mut self,
2312 thread_id: ThreadId,
2313 granularity: SteppingGranularity,
2314 cx: &mut Context<Self>,
2315 ) {
2316 self.select_historic_snapshot(None, cx);
2317
2318 let supports_single_thread_execution_requests =
2319 self.capabilities.supports_single_thread_execution_requests;
2320 let supports_stepping_granularity = self
2321 .capabilities
2322 .supports_stepping_granularity
2323 .unwrap_or_default();
2324
2325 let command = NextCommand {
2326 inner: StepCommand {
2327 thread_id: thread_id.0,
2328 granularity: supports_stepping_granularity.then(|| granularity),
2329 single_thread: supports_single_thread_execution_requests,
2330 },
2331 };
2332
2333 self.active_snapshot.thread_states.process_step(thread_id);
2334 self.request(
2335 command,
2336 Self::on_step_response::<NextCommand>(thread_id),
2337 cx,
2338 )
2339 .detach();
2340 }
2341
2342 pub fn step_in(
2343 &mut self,
2344 thread_id: ThreadId,
2345 granularity: SteppingGranularity,
2346 cx: &mut Context<Self>,
2347 ) {
2348 self.select_historic_snapshot(None, cx);
2349
2350 let supports_single_thread_execution_requests =
2351 self.capabilities.supports_single_thread_execution_requests;
2352 let supports_stepping_granularity = self
2353 .capabilities
2354 .supports_stepping_granularity
2355 .unwrap_or_default();
2356
2357 let command = StepInCommand {
2358 inner: StepCommand {
2359 thread_id: thread_id.0,
2360 granularity: supports_stepping_granularity.then(|| granularity),
2361 single_thread: supports_single_thread_execution_requests,
2362 },
2363 };
2364
2365 self.active_snapshot.thread_states.process_step(thread_id);
2366 self.request(
2367 command,
2368 Self::on_step_response::<StepInCommand>(thread_id),
2369 cx,
2370 )
2371 .detach();
2372 }
2373
2374 pub fn step_out(
2375 &mut self,
2376 thread_id: ThreadId,
2377 granularity: SteppingGranularity,
2378 cx: &mut Context<Self>,
2379 ) {
2380 self.select_historic_snapshot(None, cx);
2381
2382 let supports_single_thread_execution_requests =
2383 self.capabilities.supports_single_thread_execution_requests;
2384 let supports_stepping_granularity = self
2385 .capabilities
2386 .supports_stepping_granularity
2387 .unwrap_or_default();
2388
2389 let command = StepOutCommand {
2390 inner: StepCommand {
2391 thread_id: thread_id.0,
2392 granularity: supports_stepping_granularity.then(|| granularity),
2393 single_thread: supports_single_thread_execution_requests,
2394 },
2395 };
2396
2397 self.active_snapshot.thread_states.process_step(thread_id);
2398 self.request(
2399 command,
2400 Self::on_step_response::<StepOutCommand>(thread_id),
2401 cx,
2402 )
2403 .detach();
2404 }
2405
2406 pub fn step_back(
2407 &mut self,
2408 thread_id: ThreadId,
2409 granularity: SteppingGranularity,
2410 cx: &mut Context<Self>,
2411 ) {
2412 self.select_historic_snapshot(None, cx);
2413
2414 let supports_single_thread_execution_requests =
2415 self.capabilities.supports_single_thread_execution_requests;
2416 let supports_stepping_granularity = self
2417 .capabilities
2418 .supports_stepping_granularity
2419 .unwrap_or_default();
2420
2421 let command = StepBackCommand {
2422 inner: StepCommand {
2423 thread_id: thread_id.0,
2424 granularity: supports_stepping_granularity.then(|| granularity),
2425 single_thread: supports_single_thread_execution_requests,
2426 },
2427 };
2428
2429 self.active_snapshot.thread_states.process_step(thread_id);
2430
2431 self.request(
2432 command,
2433 Self::on_step_response::<StepBackCommand>(thread_id),
2434 cx,
2435 )
2436 .detach();
2437 }
2438
2439 pub fn stack_frames(
2440 &mut self,
2441 thread_id: ThreadId,
2442 cx: &mut Context<Self>,
2443 ) -> Result<Vec<StackFrame>> {
2444 if self.active_snapshot.thread_states.thread_status(thread_id) == ThreadStatus::Stopped
2445 && self.requests.contains_key(&ThreadsCommand.type_id())
2446 && self.active_snapshot.threads.contains_key(&thread_id)
2447 // ^ todo(debugger): We need a better way to check that we're not querying stale data
2448 // We could still be using an old thread id and have sent a new thread's request
2449 // This isn't the biggest concern right now because it hasn't caused any issues outside of tests
2450 // But it very well could cause a minor bug in the future that is hard to track down
2451 {
2452 self.fetch(
2453 super::dap_command::StackTraceCommand {
2454 thread_id: thread_id.0,
2455 start_frame: None,
2456 levels: None,
2457 },
2458 move |this, stack_frames, cx| {
2459 let entry =
2460 this.active_snapshot
2461 .threads
2462 .entry(thread_id)
2463 .and_modify(|thread| match &stack_frames {
2464 Ok(stack_frames) => {
2465 thread.stack_frames = stack_frames
2466 .iter()
2467 .cloned()
2468 .map(StackFrame::from)
2469 .collect();
2470 thread.stack_frames_error = None;
2471 }
2472 Err(error) => {
2473 thread.stack_frames.clear();
2474 thread.stack_frames_error = Some(error.to_string().into());
2475 }
2476 });
2477 debug_assert!(
2478 matches!(entry, indexmap::map::Entry::Occupied(_)),
2479 "Sent request for thread_id that doesn't exist"
2480 );
2481 if let Ok(stack_frames) = stack_frames {
2482 this.active_snapshot.stack_frames.extend(
2483 stack_frames
2484 .into_iter()
2485 .filter(|frame| {
2486 // Workaround for JavaScript debug adapter sending out "fake" stack frames for delineating await points. This is fine,
2487 // except that they always use an id of 0 for it, which collides with other (valid) stack frames.
2488 !(frame.id == 0
2489 && frame.line == 0
2490 && frame.column == 0
2491 && frame.presentation_hint
2492 == Some(StackFramePresentationHint::Label))
2493 })
2494 .map(|frame| (frame.id, StackFrame::from(frame))),
2495 );
2496 }
2497
2498 this.invalidate_command_type::<ScopesCommand>();
2499 this.invalidate_command_type::<VariablesCommand>();
2500
2501 cx.emit(SessionEvent::StackTrace);
2502 },
2503 cx,
2504 );
2505 }
2506
2507 match self.session_state().threads.get(&thread_id) {
2508 Some(thread) => {
2509 if let Some(error) = &thread.stack_frames_error {
2510 Err(anyhow!(error.to_string()))
2511 } else {
2512 Ok(thread.stack_frames.clone())
2513 }
2514 }
2515 None => Ok(Vec::new()),
2516 }
2517 }
2518
2519 pub fn scopes(&mut self, stack_frame_id: u64, cx: &mut Context<Self>) -> &[dap::Scope] {
2520 if self.requests.contains_key(&TypeId::of::<ThreadsCommand>())
2521 && self
2522 .requests
2523 .contains_key(&TypeId::of::<StackTraceCommand>())
2524 {
2525 self.fetch(
2526 ScopesCommand { stack_frame_id },
2527 move |this, scopes, cx| {
2528 let Some(scopes) = scopes.log_err() else {
2529 return
2530 };
2531
2532 for scope in scopes.iter() {
2533 this.variables(scope.variables_reference, cx);
2534 }
2535
2536 let entry = this
2537 .active_snapshot
2538 .stack_frames
2539 .entry(stack_frame_id)
2540 .and_modify(|stack_frame| {
2541 stack_frame.scopes = scopes;
2542 });
2543
2544 cx.emit(SessionEvent::Variables);
2545
2546 debug_assert!(
2547 matches!(entry, indexmap::map::Entry::Occupied(_)),
2548 "Sent scopes request for stack_frame_id that doesn't exist or hasn't been fetched"
2549 );
2550 },
2551 cx,
2552 );
2553 }
2554
2555 self.session_state()
2556 .stack_frames
2557 .get(&stack_frame_id)
2558 .map(|frame| frame.scopes.as_slice())
2559 .unwrap_or_default()
2560 }
2561
2562 pub fn variables_by_stack_frame_id(
2563 &self,
2564 stack_frame_id: StackFrameId,
2565 globals: bool,
2566 locals: bool,
2567 ) -> Vec<dap::Variable> {
2568 let state = self.session_state();
2569 let Some(stack_frame) = state.stack_frames.get(&stack_frame_id) else {
2570 return Vec::new();
2571 };
2572
2573 stack_frame
2574 .scopes
2575 .iter()
2576 .filter(|scope| {
2577 (scope.name.to_lowercase().contains("local") && locals)
2578 || (scope.name.to_lowercase().contains("global") && globals)
2579 })
2580 .filter_map(|scope| state.variables.get(&scope.variables_reference))
2581 .flatten()
2582 .cloned()
2583 .collect()
2584 }
2585
2586 pub fn watchers(&self) -> &HashMap<SharedString, Watcher> {
2587 &self.watchers
2588 }
2589
2590 pub fn add_watcher(
2591 &mut self,
2592 expression: SharedString,
2593 frame_id: u64,
2594 cx: &mut Context<Self>,
2595 ) -> Task<Result<()>> {
2596 let request = self.state.request_dap(EvaluateCommand {
2597 expression: expression.to_string(),
2598 context: Some(EvaluateArgumentsContext::Watch),
2599 frame_id: Some(frame_id),
2600 source: None,
2601 });
2602
2603 cx.spawn(async move |this, cx| {
2604 let response = request.await?;
2605
2606 this.update(cx, |session, cx| {
2607 session.watchers.insert(
2608 expression.clone(),
2609 Watcher {
2610 expression,
2611 value: response.result.into(),
2612 variables_reference: response.variables_reference,
2613 presentation_hint: response.presentation_hint,
2614 },
2615 );
2616 cx.emit(SessionEvent::Watchers);
2617 })
2618 })
2619 }
2620
2621 pub fn refresh_watchers(&mut self, frame_id: u64, cx: &mut Context<Self>) {
2622 let watches = self.watchers.clone();
2623 for (_, watch) in watches.into_iter() {
2624 self.add_watcher(watch.expression.clone(), frame_id, cx)
2625 .detach();
2626 }
2627 }
2628
2629 pub fn remove_watcher(&mut self, expression: SharedString) {
2630 self.watchers.remove(&expression);
2631 }
2632
2633 pub fn variables(
2634 &mut self,
2635 variables_reference: VariableReference,
2636 cx: &mut Context<Self>,
2637 ) -> Vec<dap::Variable> {
2638 let command = VariablesCommand {
2639 variables_reference,
2640 filter: None,
2641 start: None,
2642 count: None,
2643 format: None,
2644 };
2645
2646 self.fetch(
2647 command,
2648 move |this, variables, cx| {
2649 let Some(variables) = variables.log_err() else {
2650 return;
2651 };
2652
2653 this.active_snapshot
2654 .variables
2655 .insert(variables_reference, variables);
2656
2657 cx.emit(SessionEvent::Variables);
2658 cx.emit(SessionEvent::InvalidateInlineValue);
2659 },
2660 cx,
2661 );
2662
2663 self.session_state()
2664 .variables
2665 .get(&variables_reference)
2666 .cloned()
2667 .unwrap_or_default()
2668 }
2669
2670 pub fn data_breakpoint_info(
2671 &mut self,
2672 context: Arc<DataBreakpointContext>,
2673 mode: Option<String>,
2674 cx: &mut Context<Self>,
2675 ) -> Task<Option<dap::DataBreakpointInfoResponse>> {
2676 let command = DataBreakpointInfoCommand { context, mode };
2677
2678 self.request(command, |_, response, _| response.ok(), cx)
2679 }
2680
2681 pub fn set_variable_value(
2682 &mut self,
2683 stack_frame_id: u64,
2684 variables_reference: u64,
2685 name: String,
2686 value: String,
2687 cx: &mut Context<Self>,
2688 ) {
2689 if self.capabilities.supports_set_variable.unwrap_or_default() {
2690 self.request(
2691 SetVariableValueCommand {
2692 name,
2693 value,
2694 variables_reference,
2695 },
2696 move |this, response, cx| {
2697 let response = response.log_err()?;
2698 this.invalidate_command_type::<VariablesCommand>();
2699 this.invalidate_command_type::<ReadMemory>();
2700 this.memory.clear(cx.background_executor());
2701 this.refresh_watchers(stack_frame_id, cx);
2702 cx.emit(SessionEvent::Variables);
2703 Some(response)
2704 },
2705 cx,
2706 )
2707 .detach();
2708 }
2709 }
2710
2711 pub fn evaluate(
2712 &mut self,
2713 expression: String,
2714 context: Option<EvaluateArgumentsContext>,
2715 frame_id: Option<u64>,
2716 source: Option<Source>,
2717 cx: &mut Context<Self>,
2718 ) -> Task<()> {
2719 let event = dap::OutputEvent {
2720 category: None,
2721 output: format!("> {expression}"),
2722 group: None,
2723 variables_reference: None,
2724 source: None,
2725 line: None,
2726 column: None,
2727 data: None,
2728 location_reference: None,
2729 };
2730 self.push_output(event);
2731 let request = self.state.request_dap(EvaluateCommand {
2732 expression,
2733 context,
2734 frame_id,
2735 source,
2736 });
2737 cx.spawn(async move |this, cx| {
2738 let response = request.await;
2739 this.update(cx, |this, cx| {
2740 this.memory.clear(cx.background_executor());
2741 this.invalidate_command_type::<ReadMemory>();
2742 this.invalidate_command_type::<VariablesCommand>();
2743 cx.emit(SessionEvent::Variables);
2744 match response {
2745 Ok(response) => {
2746 let event = dap::OutputEvent {
2747 category: None,
2748 output: format!("< {}", &response.result),
2749 group: None,
2750 variables_reference: Some(response.variables_reference),
2751 source: None,
2752 line: None,
2753 column: None,
2754 data: None,
2755 location_reference: None,
2756 };
2757 this.push_output(event);
2758 }
2759 Err(e) => {
2760 let event = dap::OutputEvent {
2761 category: None,
2762 output: format!("{}", e),
2763 group: None,
2764 variables_reference: None,
2765 source: None,
2766 line: None,
2767 column: None,
2768 data: None,
2769 location_reference: None,
2770 };
2771 this.push_output(event);
2772 }
2773 };
2774 cx.notify();
2775 })
2776 .ok();
2777 })
2778 }
2779
2780 pub fn location(
2781 &mut self,
2782 reference: u64,
2783 cx: &mut Context<Self>,
2784 ) -> Option<dap::LocationsResponse> {
2785 self.fetch(
2786 LocationsCommand { reference },
2787 move |this, response, _| {
2788 let Some(response) = response.log_err() else {
2789 return;
2790 };
2791 this.active_snapshot.locations.insert(reference, response);
2792 },
2793 cx,
2794 );
2795 self.session_state().locations.get(&reference).cloned()
2796 }
2797
2798 pub fn is_attached(&self) -> bool {
2799 let SessionState::Running(local_mode) = &self.state else {
2800 return false;
2801 };
2802 local_mode.binary.request_args.request == StartDebuggingRequestArgumentsRequest::Attach
2803 }
2804
2805 pub fn disconnect_client(&mut self, cx: &mut Context<Self>) {
2806 let command = DisconnectCommand {
2807 restart: Some(false),
2808 terminate_debuggee: Some(false),
2809 suspend_debuggee: Some(false),
2810 };
2811
2812 self.request(command, Self::empty_response, cx).detach()
2813 }
2814
2815 pub fn terminate_threads(&mut self, thread_ids: Option<Vec<ThreadId>>, cx: &mut Context<Self>) {
2816 if self
2817 .capabilities
2818 .supports_terminate_threads_request
2819 .unwrap_or_default()
2820 {
2821 self.request(
2822 TerminateThreadsCommand {
2823 thread_ids: thread_ids.map(|ids| ids.into_iter().map(|id| id.0).collect()),
2824 },
2825 Self::clear_active_debug_line_response,
2826 cx,
2827 )
2828 .detach();
2829 } else {
2830 self.shutdown(cx).detach();
2831 }
2832 }
2833
2834 pub fn thread_state(&self, thread_id: ThreadId) -> Option<ThreadStatus> {
2835 self.session_state().thread_states.thread_state(thread_id)
2836 }
2837
2838 pub fn quirks(&self) -> SessionQuirks {
2839 self.quirks
2840 }
2841
2842 fn launch_browser_for_remote_server(
2843 &mut self,
2844 mut request: LaunchBrowserInCompanionParams,
2845 cx: &mut Context<Self>,
2846 ) {
2847 let Some(remote_client) = self.remote_client.clone() else {
2848 log::error!("can't launch browser in companion for non-remote project");
2849 return;
2850 };
2851 let Some(http_client) = self.http_client.clone() else {
2852 return;
2853 };
2854 let Some(node_runtime) = self.node_runtime.clone() else {
2855 return;
2856 };
2857
2858 let mut console_output = self.console_output(cx);
2859 let task = cx.spawn(async move |this, cx| {
2860 let forward_ports_process = if remote_client
2861 .read_with(cx, |client, _| client.shares_network_interface())?
2862 {
2863 request.other.insert(
2864 "proxyUri".into(),
2865 format!("127.0.0.1:{}", request.server_port).into(),
2866 );
2867 None
2868 } else {
2869 let port = TcpTransport::unused_port(Ipv4Addr::LOCALHOST)
2870 .await
2871 .context("getting port for DAP")?;
2872 request
2873 .other
2874 .insert("proxyUri".into(), format!("127.0.0.1:{port}").into());
2875 let mut port_forwards = vec![(port, "localhost".to_owned(), request.server_port)];
2876
2877 if let Some(value) = request.params.get("url")
2878 && let Some(url) = value.as_str()
2879 && let Some(url) = Url::parse(url).ok()
2880 && let Some(frontend_port) = url.port()
2881 {
2882 port_forwards.push((frontend_port, "localhost".to_owned(), frontend_port));
2883 }
2884
2885 let child = remote_client.update(cx, |client, _| {
2886 let command = client.build_forward_ports_command(port_forwards)?;
2887 let child = new_smol_command(command.program)
2888 .args(command.args)
2889 .envs(command.env)
2890 .spawn()
2891 .context("spawning port forwarding process")?;
2892 anyhow::Ok(child)
2893 })??;
2894 Some(child)
2895 };
2896
2897 let mut companion_process = None;
2898 let companion_port =
2899 if let Some(companion_port) = this.read_with(cx, |this, _| this.companion_port)? {
2900 companion_port
2901 } else {
2902 let task = cx.spawn(async move |cx| spawn_companion(node_runtime, cx).await);
2903 match task.await {
2904 Ok((port, child)) => {
2905 companion_process = Some(child);
2906 port
2907 }
2908 Err(e) => {
2909 console_output
2910 .send(format!("Failed to launch browser companion process: {e}"))
2911 .await
2912 .ok();
2913 return Err(e);
2914 }
2915 }
2916 };
2917
2918 let mut background_tasks = Vec::new();
2919 if let Some(mut forward_ports_process) = forward_ports_process {
2920 background_tasks.push(cx.spawn(async move |_| {
2921 forward_ports_process.status().await.log_err();
2922 }));
2923 };
2924 if let Some(mut companion_process) = companion_process {
2925 if let Some(stderr) = companion_process.stderr.take() {
2926 let mut console_output = console_output.clone();
2927 background_tasks.push(cx.spawn(async move |_| {
2928 let mut stderr = BufReader::new(stderr);
2929 let mut line = String::new();
2930 while let Ok(n) = stderr.read_line(&mut line).await
2931 && n > 0
2932 {
2933 console_output
2934 .send(format!("companion stderr: {line}"))
2935 .await
2936 .ok();
2937 line.clear();
2938 }
2939 }));
2940 }
2941 background_tasks.push(cx.spawn({
2942 let mut console_output = console_output.clone();
2943 async move |_| match companion_process.status().await {
2944 Ok(status) => {
2945 if status.success() {
2946 console_output
2947 .send("Companion process exited normally".into())
2948 .await
2949 .ok();
2950 } else {
2951 console_output
2952 .send(format!(
2953 "Companion process exited abnormally with {status:?}"
2954 ))
2955 .await
2956 .ok();
2957 }
2958 }
2959 Err(e) => {
2960 console_output
2961 .send(format!("Failed to join companion process: {e}"))
2962 .await
2963 .ok();
2964 }
2965 }
2966 }));
2967 }
2968
2969 // TODO pass wslInfo as needed
2970
2971 let companion_address = format!("127.0.0.1:{companion_port}");
2972 let mut companion_started = false;
2973 for _ in 0..10 {
2974 if TcpStream::connect(&companion_address).await.is_ok() {
2975 companion_started = true;
2976 break;
2977 }
2978 cx.background_executor()
2979 .timer(Duration::from_millis(100))
2980 .await;
2981 }
2982 if !companion_started {
2983 console_output
2984 .send("Browser companion failed to start".into())
2985 .await
2986 .ok();
2987 bail!("Browser companion failed to start");
2988 }
2989
2990 let response = http_client
2991 .post_json(
2992 &format!("http://{companion_address}/launch-and-attach"),
2993 serde_json::to_string(&request)
2994 .context("serializing request")?
2995 .into(),
2996 )
2997 .await;
2998 match response {
2999 Ok(response) => {
3000 if !response.status().is_success() {
3001 console_output
3002 .send("Launch request to companion failed".into())
3003 .await
3004 .ok();
3005 return Err(anyhow!("launch request failed"));
3006 }
3007 }
3008 Err(e) => {
3009 console_output
3010 .send("Failed to read response from companion".into())
3011 .await
3012 .ok();
3013 return Err(e);
3014 }
3015 }
3016
3017 this.update(cx, |this, _| {
3018 this.background_tasks.extend(background_tasks);
3019 this.companion_port = Some(companion_port);
3020 })?;
3021
3022 anyhow::Ok(())
3023 });
3024 self.background_tasks.push(cx.spawn(async move |_, _| {
3025 task.await.log_err();
3026 }));
3027 }
3028
3029 fn kill_browser(&self, request: KillCompanionBrowserParams, cx: &mut App) {
3030 let Some(companion_port) = self.companion_port else {
3031 log::error!("received killCompanionBrowser but js-debug-companion is not running");
3032 return;
3033 };
3034 let Some(http_client) = self.http_client.clone() else {
3035 return;
3036 };
3037
3038 cx.spawn(async move |_| {
3039 http_client
3040 .post_json(
3041 &format!("http://127.0.0.1:{companion_port}/kill"),
3042 serde_json::to_string(&request)
3043 .context("serializing request")?
3044 .into(),
3045 )
3046 .await?;
3047 anyhow::Ok(())
3048 })
3049 .detach_and_log_err(cx)
3050 }
3051}
3052
3053#[derive(Serialize, Deserialize, Debug)]
3054#[serde(rename_all = "camelCase")]
3055struct LaunchBrowserInCompanionParams {
3056 server_port: u16,
3057 params: HashMap<String, serde_json::Value>,
3058 #[serde(flatten)]
3059 other: HashMap<String, serde_json::Value>,
3060}
3061
3062#[derive(Serialize, Deserialize, Debug)]
3063#[serde(rename_all = "camelCase")]
3064struct KillCompanionBrowserParams {
3065 launch_id: u64,
3066}
3067
3068async fn spawn_companion(
3069 node_runtime: NodeRuntime,
3070 cx: &mut AsyncApp,
3071) -> Result<(u16, smol::process::Child)> {
3072 let binary_path = node_runtime
3073 .binary_path()
3074 .await
3075 .context("getting node path")?;
3076 let path = cx
3077 .spawn(async move |cx| get_or_install_companion(node_runtime, cx).await)
3078 .await?;
3079 log::info!("will launch js-debug-companion version {path:?}");
3080
3081 let port = {
3082 let listener = TcpListener::bind("127.0.0.1:0")
3083 .await
3084 .context("getting port for companion")?;
3085 listener.local_addr()?.port()
3086 };
3087
3088 let dir = paths::data_dir()
3089 .join("js_debug_companion_state")
3090 .to_string_lossy()
3091 .to_string();
3092
3093 let child = new_smol_command(binary_path)
3094 .arg(path)
3095 .args([
3096 format!("--listen=127.0.0.1:{port}"),
3097 format!("--state={dir}"),
3098 ])
3099 .stdin(Stdio::piped())
3100 .stdout(Stdio::piped())
3101 .stderr(Stdio::piped())
3102 .spawn()
3103 .context("spawning companion child process")?;
3104
3105 Ok((port, child))
3106}
3107
3108async fn get_or_install_companion(node: NodeRuntime, cx: &mut AsyncApp) -> Result<PathBuf> {
3109 const PACKAGE_NAME: &str = "@zed-industries/js-debug-companion-cli";
3110
3111 async fn install_latest_version(dir: PathBuf, node: NodeRuntime) -> Result<PathBuf> {
3112 let temp_dir = tempfile::tempdir().context("creating temporary directory")?;
3113 node.npm_install_packages(temp_dir.path(), &[(PACKAGE_NAME, "latest")])
3114 .await
3115 .context("installing latest companion package")?;
3116 let version = node
3117 .npm_package_installed_version(temp_dir.path(), PACKAGE_NAME)
3118 .await
3119 .context("getting installed companion version")?
3120 .context("companion was not installed")?;
3121 smol::fs::rename(temp_dir.path(), dir.join(&version))
3122 .await
3123 .context("moving companion package into place")?;
3124 Ok(dir.join(version))
3125 }
3126
3127 let dir = paths::debug_adapters_dir().join("js-debug-companion");
3128 let (latest_installed_version, latest_version) = cx
3129 .background_spawn({
3130 let dir = dir.clone();
3131 let node = node.clone();
3132 async move {
3133 smol::fs::create_dir_all(&dir)
3134 .await
3135 .context("creating companion installation directory")?;
3136
3137 let mut children = smol::fs::read_dir(&dir)
3138 .await
3139 .context("reading companion installation directory")?
3140 .try_collect::<Vec<_>>()
3141 .await
3142 .context("reading companion installation directory entries")?;
3143 children
3144 .sort_by_key(|child| semver::Version::parse(child.file_name().to_str()?).ok());
3145
3146 let latest_installed_version = children.last().and_then(|child| {
3147 let version = child.file_name().into_string().ok()?;
3148 Some((child.path(), version))
3149 });
3150 let latest_version = node
3151 .npm_package_latest_version(PACKAGE_NAME)
3152 .await
3153 .log_err();
3154 anyhow::Ok((latest_installed_version, latest_version))
3155 }
3156 })
3157 .await?;
3158
3159 let path = if let Some((installed_path, installed_version)) = latest_installed_version {
3160 if let Some(latest_version) = latest_version
3161 && latest_version != installed_version
3162 {
3163 cx.background_spawn(install_latest_version(dir.clone(), node.clone()))
3164 .detach();
3165 }
3166 Ok(installed_path)
3167 } else {
3168 cx.background_spawn(install_latest_version(dir.clone(), node.clone()))
3169 .await
3170 };
3171
3172 Ok(path?
3173 .join("node_modules")
3174 .join(PACKAGE_NAME)
3175 .join("out")
3176 .join("cli.js"))
3177}