DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/Environment/
DebugProvider.rs1use std::sync::Arc;
42
43use CommonLibrary::{
44 Debug::DebugService::DebugService,
45 Environment::Requires::Requires,
46 Error::CommonError::CommonError,
47 IPC::{DTO::ProxyTarget::ProxyTarget, IPCProvider::IPCProvider},
48};
49use async_trait::async_trait;
50use serde_json::{Value, json};
51use tauri::Emitter;
52use url::Url;
53
54use super::MountainEnvironment::MountainEnvironment;
55use crate::dev_log;
56
57#[async_trait]
58impl DebugService for MountainEnvironment {
59 async fn RegisterDebugConfigurationProvider(
60 &self,
61
62 DebugType:String,
63
64 ProviderHandle:u32,
65
66 SideCarIdentifier:String,
67 ) -> Result<(), CommonError> {
68 if DebugType.is_empty() {
70 return Err(CommonError::InvalidArgument {
71 ArgumentName:"DebugType".to_string(),
72 Reason:"DebugType cannot be empty".to_string(),
73 });
74 }
75
76 dev_log!(
77 "exthost",
78 "[DebugProvider] Registering DebugConfigurationProvider for type '{}' (handle: {}, sidecar: {})",
79 DebugType,
80 ProviderHandle,
81 SideCarIdentifier
82 );
83
84 self.ApplicationState
86 .Feature
87 .Debug
88 .RegisterDebugConfigurationProvider(DebugType, ProviderHandle, SideCarIdentifier)
89 .map_err(|e| CommonError::Unknown { Description:e })?;
90
91 Ok(())
92 }
93
94 async fn RegisterDebugAdapterDescriptorFactory(
95 &self,
96
97 DebugType:String,
98
99 FactoryHandle:u32,
100
101 SideCarIdentifier:String,
102 ) -> Result<(), CommonError> {
103 if DebugType.is_empty() {
105 return Err(CommonError::InvalidArgument {
106 ArgumentName:"DebugType".to_string(),
107 Reason:"DebugType cannot be empty".to_string(),
108 });
109 }
110
111 dev_log!(
112 "exthost",
113 "[DebugProvider] Registering DebugAdapterDescriptorFactory for type '{}' (handle: {}, sidecar: {})",
114 DebugType,
115 FactoryHandle,
116 SideCarIdentifier
117 );
118
119 self.ApplicationState
121 .Feature
122 .Debug
123 .RegisterDebugAdapterDescriptorFactory(DebugType, FactoryHandle, SideCarIdentifier)
124 .map_err(|e| CommonError::Unknown { Description:e })?;
125
126 Ok(())
127 }
128
129 async fn StartDebugging(&self, _FolderURI:Option<Url>, Configuration:Value) -> Result<String, CommonError> {
130 let SessionID = uuid::Uuid::new_v4().to_string();
131
132 dev_log!(
133 "exthost",
134 "[DebugProvider] Starting debug session '{}' with config: {:?}",
135 SessionID,
136 Configuration
137 );
138
139 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
140
141 let DebugType = Configuration
142 .get("type")
143 .and_then(Value::as_str)
144 .ok_or_else(|| {
145 CommonError::InvalidArgument {
146 ArgumentName:"Configuration".into(),
147
148 Reason:"Missing 'type' field in debug configuration.".into(),
149 }
150 })?
151 .to_string();
152
153 let TargetSideCar = self
157 .ApplicationState
158 .Feature
159 .Debug
160 .GetDebugConfigurationProvider(&DebugType)
161 .map(|R| R.SideCarIdentifier.clone())
162 .unwrap_or_else(|| "cocoon-main".to_string());
163
164 dev_log!(
166 "exthost",
167 "[DebugProvider] Resolving debug configuration for type '{}'",
168 DebugType
169 );
170
171 dev_log!("exthost", "[DebugProvider] Resolving debug configuration...");
172
173 let ResolveConfigMethod = format!("{}$resolveDebugConfiguration", ProxyTarget::ExtHostDebug.GetTargetPrefix());
174
175 let ResolvedConfig = IPCProvider
176 .SendRequestToSideCar(
177 TargetSideCar.clone(),
178 ResolveConfigMethod,
179 json!([DebugType.clone(), Configuration]),
180 5000,
181 )
182 .await?;
183
184 dev_log!("exthost", "[DebugProvider] Creating debug adapter descriptor...");
186
187 let CreateDescriptorMethod =
188 format!("{}$createDebugAdapterDescriptor", ProxyTarget::ExtHostDebug.GetTargetPrefix());
189
190 let Descriptor = IPCProvider
191 .SendRequestToSideCar(
192 TargetSideCar.clone(),
193 CreateDescriptorMethod,
194 json!([DebugType, &ResolvedConfig]),
195 5000,
196 )
197 .await?;
198
199 dev_log!(
201 "exthost",
202 "[DebugProvider] Spawning Debug Adapter based on descriptor: {:?}",
203 Descriptor
204 );
205
206 let DescriptorType = Descriptor.get("type").and_then(Value::as_str).unwrap_or("").to_string();
223
224 let AdapterStdinSender:Option<tokio::sync::mpsc::UnboundedSender<Vec<u8>>>;
225
226 let AdapterChildPid:Option<u32>;
227
228 match DescriptorType.as_str() {
229 "executable" => {
230 let Command = Descriptor
231 .get("command")
232 .and_then(Value::as_str)
233 .ok_or_else(|| {
234 CommonError::InvalidArgument {
235 ArgumentName:"Descriptor.command".into(),
236 Reason:"executable adapter descriptor missing 'command'".into(),
237 }
238 })?
239 .to_string();
240
241 let Args:Vec<String> = Descriptor
242 .get("args")
243 .and_then(Value::as_array)
244 .map(|A| A.iter().filter_map(|V| V.as_str().map(str::to_string)).collect())
245 .unwrap_or_default();
246
247 let OptionsValue = Descriptor.get("options").cloned().unwrap_or(Value::Null);
248
249 let Cwd = OptionsValue.get("cwd").and_then(Value::as_str).map(str::to_string);
250
251 let EnvOverrides:Vec<(String, String)> = OptionsValue
252 .get("env")
253 .and_then(Value::as_object)
254 .map(|O| {
255 O.iter()
256 .filter_map(|(K, V)| V.as_str().map(|S| (K.clone(), S.to_string())))
257 .collect()
258 })
259 .unwrap_or_default();
260
261 let mut Builder = tokio::process::Command::new(&Command);
262
263 Builder
264 .args(&Args)
265 .stdin(std::process::Stdio::piped())
266 .stdout(std::process::Stdio::piped())
267 .stderr(std::process::Stdio::piped());
268
269 if let Some(CwdPath) = &Cwd {
270 Builder.current_dir(CwdPath);
271 }
272
273 for (Key, Value) in &EnvOverrides {
274 Builder.env(Key, Value);
275 }
276
277 let mut Child = Builder.spawn().map_err(|Error| {
278 CommonError::IPCError {
279 Description:format!(
280 "Failed to spawn debug adapter '{}' for session {}: {}",
281 Command, SessionID, Error
282 ),
283 }
284 })?;
285
286 let Pid = Child.id();
287
288 let Stdin = Child.stdin.take().ok_or_else(|| {
289 CommonError::IPCError { Description:format!("Adapter for session {} had no stdin pipe", SessionID) }
290 })?;
291
292 let Stdout = Child.stdout.take().ok_or_else(|| {
293 CommonError::IPCError {
294 Description:format!("Adapter for session {} had no stdout pipe", SessionID),
295 }
296 })?;
297
298 let Stderr = Child.stderr.take().ok_or_else(|| {
299 CommonError::IPCError {
300 Description:format!("Adapter for session {} had no stderr pipe", SessionID),
301 }
302 })?;
303
304 let (Sender, mut Receiver) = tokio::sync::mpsc::unbounded_channel::<Vec<u8>>();
305
306 let StdinSessionId = SessionID.clone();
311
312 tokio::spawn(async move {
313 use tokio::io::AsyncWriteExt;
314 let mut Pipe = Stdin;
315 while let Some(Frame) = Receiver.recv().await {
316 if let Err(Error) = Pipe.write_all(&Frame).await {
317 crate::dev_log!(
318 "exthost",
319 "warn: [DebugAdapter] stdin write failed for session {}: {}",
320 StdinSessionId,
321 Error
322 );
323 break;
324 }
325 if let Err(Error) = Pipe.flush().await {
326 crate::dev_log!(
327 "exthost",
328 "warn: [DebugAdapter] stdin flush failed for session {}: {}",
329 StdinSessionId,
330 Error
331 );
332 break;
333 }
334 }
335 let _ = Pipe.shutdown().await;
336 });
337
338 let StdoutSessionId = SessionID.clone();
345
346 let StdoutHandle = self.ApplicationHandle.clone();
347
348 let StdoutSidecar = TargetSideCar.clone();
349
350 tokio::spawn(async move {
351 use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
352 let mut Reader = BufReader::new(Stdout);
353 let mut Header = String::new();
354 loop {
355 Header.clear();
356 let mut ContentLength:usize = 0;
357 loop {
358 Header.clear();
359 match Reader.read_line(&mut Header).await {
360 Ok(0) => return, Ok(_) => {},
362 Err(Error) => {
363 crate::dev_log!(
364 "exthost",
365 "warn: [DebugAdapter] stdout read failed for session {}: {}",
366 StdoutSessionId,
367 Error
368 );
369 return;
370 },
371 }
372 let Trimmed = Header.trim_end_matches("\r\n").trim_end_matches('\n');
373 if Trimmed.is_empty() {
374 break;
375 }
376 if let Some(Rest) = Trimmed.strip_prefix("Content-Length:") {
377 if let Ok(N) = Rest.trim().parse::<usize>() {
378 ContentLength = N;
379 }
380 }
381 }
382 if ContentLength == 0 {
383 continue;
384 }
385 let mut Body = vec![0u8; ContentLength];
386 if let Err(Error) = Reader.read_exact(&mut Body).await {
387 crate::dev_log!(
388 "exthost",
389 "warn: [DebugAdapter] stdout body read failed for session {}: {}",
390 StdoutSessionId,
391 Error
392 );
393 return;
394 }
395 let Parsed:Value = serde_json::from_slice(&Body).unwrap_or(Value::Null);
396 let _ = StdoutHandle.emit(
397 "sky://debug/dap-message",
398 json!({
399 "sessionId": StdoutSessionId,
400 "sidecarId": StdoutSidecar,
401 "message": Parsed,
402 }),
403 );
404 }
405 });
406
407 let StderrSessionId = SessionID.clone();
410
411 tokio::spawn(async move {
412 use tokio::io::{AsyncBufReadExt, BufReader};
413 let mut Lines = BufReader::new(Stderr).lines();
414 while let Ok(Some(Line)) = Lines.next_line().await {
415 crate::dev_log!("exthost", "[DebugAdapter] stderr session={}: {}", StderrSessionId, Line);
416 }
417 });
418
419 AdapterStdinSender = Some(Sender);
420
421 AdapterChildPid = Pid;
422
423 dev_log!(
424 "exthost",
425 "[DebugProvider] Spawned executable adapter for session '{}' pid={:?} command={:?}",
426 SessionID,
427 Pid,
428 Command
429 );
430 },
431
432 "server" | "pipeServer" => {
433 dev_log!(
434 "exthost",
435 "warn: [DebugProvider] Adapter type '{}' not yet wired (session '{}'). Reverse-RPC dispatch only.",
436 DescriptorType,
437 SessionID
438 );
439
440 AdapterStdinSender = None;
441
442 AdapterChildPid = None;
443 },
444
445 "implementation" => {
446 dev_log!(
447 "exthost",
448 "[DebugProvider] Inline implementation adapter for session '{}' - DAP frames travel via Cocoon \
449 reverse-RPC.",
450 SessionID
451 );
452
453 AdapterStdinSender = None;
454
455 AdapterChildPid = None;
456 },
457
458 _ => {
459 dev_log!(
460 "exthost",
461 "warn: [DebugProvider] Unknown adapter descriptor type '{}' for session '{}' - registering \
462 session without spawn.",
463 DescriptorType,
464 SessionID
465 );
466
467 AdapterStdinSender = None;
468
469 AdapterChildPid = None;
470 },
471 }
472
473 if let Err(RegError) = self.ApplicationState.Feature.Debug.RegisterDebugSession(
478 crate::ApplicationState::State::FeatureState::Debug::DebugState::DebugSessionEntry {
479 SessionId:SessionID.clone(),
480 DebugType:DebugType.clone(),
481 SideCarIdentifier:TargetSideCar.clone(),
482 StdinSender:AdapterStdinSender,
483 ChildPid:AdapterChildPid,
484 },
485 ) {
486 dev_log!(
487 "exthost",
488 "warn: [DebugProvider] Failed to register session '{}' in DebugState: {}",
489 SessionID,
490 RegError
491 );
492 }
493
494 let StartedMethod = format!("{}$onDidStartDebugSession", ProxyTarget::ExtHostDebug.GetTargetPrefix());
504
505 let StartedSession = json!({
506 "id": SessionID.clone(),
507 "type": DebugType.clone(),
508 "name": ResolvedConfig.get("name").and_then(Value::as_str).unwrap_or(&DebugType),
509 "configuration": ResolvedConfig.clone(),
510 });
511
512 if let Err(error) = IPCProvider
513 .SendNotificationToSideCar(TargetSideCar.clone(), StartedMethod, json!([StartedSession]))
514 .await
515 {
516 dev_log!(
517 "exthost",
518 "warn: [DebugProvider] StartDebugging notification failed for '{}': {:?}",
519 SessionID,
520 error
521 );
522 }
523
524 let _ = self.ApplicationHandle.emit(
530 "sky://debug/sessionStart",
531 json!({
532 "sessionId": SessionID.clone(),
533 "type": DebugType.clone(),
534 "configuration": ResolvedConfig.clone(),
535 }),
536 );
537
538 dev_log!("exthost", "[DebugProvider] Debug session '{}' started (simulation).", SessionID);
539
540 Ok(SessionID)
541 }
542
543 async fn SendCommand(&self, SessionID:String, Command:String, Arguments:Value) -> Result<Value, CommonError> {
544 dev_log!(
545 "exthost",
546 "[DebugProvider] SendCommand for session '{}' (command: '{}', args: {:?})",
547 SessionID,
548 Command,
549 Arguments
550 );
551
552 let SessionEntry = self.ApplicationState.Feature.Debug.GetDebugSession(&SessionID);
557
558 let RequestSeq = Arguments.get("seq").and_then(Value::as_u64).unwrap_or(0);
569
570 let RequestArguments = Arguments.get("arguments").cloned().unwrap_or(Arguments.clone());
571
572 let DapRequest = json!({
573 "seq": RequestSeq,
574 "type": "request",
575 "command": Command,
576 "arguments": RequestArguments,
577 });
578
579 if let Some(Entry) = SessionEntry.as_ref() {
580 if let Some(Sender) = Entry.StdinSender.as_ref() {
581 let Body = serde_json::to_vec(&DapRequest).map_err(|Error| {
582 CommonError::IPCError {
583 Description:format!("Failed to serialize DAP request for session {}: {}", SessionID, Error),
584 }
585 })?;
586
587 let Header = format!("Content-Length: {}\r\n\r\n", Body.len());
588
589 let mut Frame = Vec::with_capacity(Header.len() + Body.len());
590
591 Frame.extend_from_slice(Header.as_bytes());
592
593 Frame.extend_from_slice(&Body);
594
595 Sender.send(Frame).map_err(|Error| {
596 CommonError::IPCError {
597 Description:format!("Adapter stdin channel for session {} closed: {}", SessionID, Error),
598 }
599 })?;
600
601 return Ok(json!({
608 "success": true,
609 "sessionId": SessionID,
610 "command": Command,
611 "transport": "stdio",
612 }));
613 }
614 }
615
616 let TargetSidecar = SessionEntry
625 .as_ref()
626 .map(|E| E.SideCarIdentifier.clone())
627 .unwrap_or_else(|| "cocoon-main".to_string());
628
629 let SendDapMethod = format!("{}$sendDAPRequest", ProxyTarget::ExtHostDebug.GetTargetPrefix());
630
631 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
632
633 match IPCProvider
634 .SendRequestToSideCar(
635 TargetSidecar,
636 SendDapMethod,
637 json!([{ "sessionId": SessionID, "request": DapRequest }]),
638 15000,
639 )
640 .await
641 {
642 Ok(Response) => Ok(Response),
643
644 Err(Error) => {
645 dev_log!(
646 "exthost",
647 "warn: [DebugProvider] reverse-RPC SendCommand failed for session {}: {:?}",
648 SessionID,
649 Error
650 );
651
652 Err(Error)
653 },
654 }
655 }
656
657 async fn StopDebugging(&self, SessionID:String) -> Result<(), CommonError> {
658 dev_log!("exthost", "[DebugProvider] StopDebugging request for session '{}'", SessionID);
659
660 if let Some(Entry) = self.ApplicationState.Feature.Debug.GetDebugSession(&SessionID) {
665 if let Some(Sender) = Entry.StdinSender.as_ref() {
666 let DisconnectRequest = json!({
667 "seq": 0,
668 "type": "request",
669 "command": "disconnect",
670 "arguments": { "restart": false, "terminateDebuggee": true },
671 });
672
673 if let Ok(Body) = serde_json::to_vec(&DisconnectRequest) {
674 let Header = format!("Content-Length: {}\r\n\r\n", Body.len());
675
676 let mut Frame = Vec::with_capacity(Header.len() + Body.len());
677
678 Frame.extend_from_slice(Header.as_bytes());
679
680 Frame.extend_from_slice(&Body);
681
682 let _ = Sender.send(Frame);
683 }
684 }
685 }
686
687 let _ = self.ApplicationState.Feature.Debug.UnregisterDebugSession(&SessionID);
692
693 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
694
695 let TerminateMethod = format!("{}$onDidTerminateDebugSession", ProxyTarget::ExtHostDebug.GetTargetPrefix());
696
697 if let Err(error) = IPCProvider
698 .SendNotificationToSideCar("cocoon-main".to_string(), TerminateMethod, json!([{ "id": SessionID.clone() }]))
699 .await
700 {
701 dev_log!(
702 "exthost",
703 "warn: [DebugProvider] StopDebugging notification failed for '{}': {:?}",
704 SessionID,
705 error
706 );
707 }
708
709 let _ = self
710 .ApplicationHandle
711 .emit("sky://debug/sessionEnd", json!({ "sessionId": SessionID.clone() }));
712
713 Ok(())
714 }
715}