1use 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 = "cocoon-main".to_string();
157
158 dev_log!(
160 "exthost",
161 "[DebugProvider] Resolving debug configuration for type '{}'",
162 DebugType
163 );
164
165 dev_log!("exthost", "[DebugProvider] Resolving debug configuration...");
166
167 let ResolveConfigMethod = format!("{}$resolveDebugConfiguration", ProxyTarget::ExtHostDebug.GetTargetPrefix());
168
169 let ResolvedConfig = IPCProvider
170 .SendRequestToSideCar(
171 TargetSideCar.clone(),
172 ResolveConfigMethod,
173 json!([DebugType.clone(), Configuration]),
174 5000,
175 )
176 .await?;
177
178 dev_log!("exthost", "[DebugProvider] Creating debug adapter descriptor...");
180
181 let CreateDescriptorMethod =
182 format!("{}$createDebugAdapterDescriptor", ProxyTarget::ExtHostDebug.GetTargetPrefix());
183
184 let Descriptor = IPCProvider
185 .SendRequestToSideCar(
186 TargetSideCar.clone(),
187 CreateDescriptorMethod,
188 json!([DebugType, &ResolvedConfig]),
189 5000,
190 )
191 .await?;
192
193 dev_log!(
195 "exthost",
196 "[DebugProvider] Spawning Debug Adapter based on descriptor: {:?}",
197 Descriptor
198 );
199
200 let DescriptorType = Descriptor.get("type").and_then(Value::as_str).unwrap_or("").to_string();
217
218 let AdapterStdinSender:Option<tokio::sync::mpsc::UnboundedSender<Vec<u8>>>;
219
220 let AdapterChildPid:Option<u32>;
221
222 match DescriptorType.as_str() {
223 "executable" => {
224 let Command = Descriptor
225 .get("command")
226 .and_then(Value::as_str)
227 .ok_or_else(|| {
228 CommonError::InvalidArgument {
229 ArgumentName:"Descriptor.command".into(),
230 Reason:"executable adapter descriptor missing 'command'".into(),
231 }
232 })?
233 .to_string();
234
235 let Args:Vec<String> = Descriptor
236 .get("args")
237 .and_then(Value::as_array)
238 .map(|A| A.iter().filter_map(|V| V.as_str().map(str::to_string)).collect())
239 .unwrap_or_default();
240
241 let OptionsValue = Descriptor.get("options").cloned().unwrap_or(Value::Null);
242
243 let Cwd = OptionsValue.get("cwd").and_then(Value::as_str).map(str::to_string);
244
245 let EnvOverrides:Vec<(String, String)> = OptionsValue
246 .get("env")
247 .and_then(Value::as_object)
248 .map(|O| {
249 O.iter()
250 .filter_map(|(K, V)| V.as_str().map(|S| (K.clone(), S.to_string())))
251 .collect()
252 })
253 .unwrap_or_default();
254
255 let mut Builder = tokio::process::Command::new(&Command);
256
257 Builder
258 .args(&Args)
259 .stdin(std::process::Stdio::piped())
260 .stdout(std::process::Stdio::piped())
261 .stderr(std::process::Stdio::piped());
262
263 if let Some(CwdPath) = &Cwd {
264 Builder.current_dir(CwdPath);
265 }
266
267 for (Key, Value) in &EnvOverrides {
268 Builder.env(Key, Value);
269 }
270
271 let mut Child = Builder.spawn().map_err(|Error| {
272 CommonError::IPCError {
273 Description:format!(
274 "Failed to spawn debug adapter '{}' for session {}: {}",
275 Command, SessionID, Error
276 ),
277 }
278 })?;
279
280 let Pid = Child.id();
281
282 let Stdin = Child.stdin.take().ok_or_else(|| {
283 CommonError::IPCError { Description:format!("Adapter for session {} had no stdin pipe", SessionID) }
284 })?;
285
286 let Stdout = Child.stdout.take().ok_or_else(|| {
287 CommonError::IPCError {
288 Description:format!("Adapter for session {} had no stdout pipe", SessionID),
289 }
290 })?;
291
292 let Stderr = Child.stderr.take().ok_or_else(|| {
293 CommonError::IPCError {
294 Description:format!("Adapter for session {} had no stderr pipe", SessionID),
295 }
296 })?;
297
298 let (Sender, mut Receiver) = tokio::sync::mpsc::unbounded_channel::<Vec<u8>>();
299
300 let StdinSessionId = SessionID.clone();
305
306 tokio::spawn(async move {
307 use tokio::io::AsyncWriteExt;
308 let mut Pipe = Stdin;
309 while let Some(Frame) = Receiver.recv().await {
310 if let Err(Error) = Pipe.write_all(&Frame).await {
311 crate::dev_log!(
312 "exthost",
313 "warn: [DebugAdapter] stdin write failed for session {}: {}",
314 StdinSessionId,
315 Error
316 );
317 break;
318 }
319 if let Err(Error) = Pipe.flush().await {
320 crate::dev_log!(
321 "exthost",
322 "warn: [DebugAdapter] stdin flush failed for session {}: {}",
323 StdinSessionId,
324 Error
325 );
326 break;
327 }
328 }
329 let _ = Pipe.shutdown().await;
330 });
331
332 let StdoutSessionId = SessionID.clone();
339
340 let StdoutHandle = self.ApplicationHandle.clone();
341
342 let StdoutSidecar = TargetSideCar.clone();
343
344 tokio::spawn(async move {
345 use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
346 let mut Reader = BufReader::new(Stdout);
347 let mut Header = String::new();
348 loop {
349 Header.clear();
350 let mut ContentLength:usize = 0;
351 loop {
352 Header.clear();
353 match Reader.read_line(&mut Header).await {
354 Ok(0) => return, Ok(_) => {},
356 Err(Error) => {
357 crate::dev_log!(
358 "exthost",
359 "warn: [DebugAdapter] stdout read failed for session {}: {}",
360 StdoutSessionId,
361 Error
362 );
363 return;
364 },
365 }
366 let Trimmed = Header.trim_end_matches("\r\n").trim_end_matches('\n');
367 if Trimmed.is_empty() {
368 break;
369 }
370 if let Some(Rest) = Trimmed.strip_prefix("Content-Length:") {
371 if let Ok(N) = Rest.trim().parse::<usize>() {
372 ContentLength = N;
373 }
374 }
375 }
376 if ContentLength == 0 {
377 continue;
378 }
379 let mut Body = vec![0u8; ContentLength];
380 if let Err(Error) = Reader.read_exact(&mut Body).await {
381 crate::dev_log!(
382 "exthost",
383 "warn: [DebugAdapter] stdout body read failed for session {}: {}",
384 StdoutSessionId,
385 Error
386 );
387 return;
388 }
389 let Parsed:Value = serde_json::from_slice(&Body).unwrap_or(Value::Null);
390 let _ = StdoutHandle.emit(
391 "sky://debug/dap-message",
392 json!({
393 "sessionId": StdoutSessionId,
394 "sidecarId": StdoutSidecar,
395 "message": Parsed,
396 }),
397 );
398 }
399 });
400
401 let StderrSessionId = SessionID.clone();
404
405 tokio::spawn(async move {
406 use tokio::io::{AsyncBufReadExt, BufReader};
407 let mut Lines = BufReader::new(Stderr).lines();
408 while let Ok(Some(Line)) = Lines.next_line().await {
409 crate::dev_log!("exthost", "[DebugAdapter] stderr session={}: {}", StderrSessionId, Line);
410 }
411 });
412
413 AdapterStdinSender = Some(Sender);
414
415 AdapterChildPid = Pid;
416
417 dev_log!(
418 "exthost",
419 "[DebugProvider] Spawned executable adapter for session '{}' pid={:?} command={:?}",
420 SessionID,
421 Pid,
422 Command
423 );
424 },
425
426 "server" | "pipeServer" => {
427 dev_log!(
428 "exthost",
429 "warn: [DebugProvider] Adapter type '{}' not yet wired (session '{}'). Reverse-RPC dispatch only.",
430 DescriptorType,
431 SessionID
432 );
433
434 AdapterStdinSender = None;
435
436 AdapterChildPid = None;
437 },
438
439 "implementation" => {
440 dev_log!(
441 "exthost",
442 "[DebugProvider] Inline implementation adapter for session '{}' - DAP frames travel via Cocoon \
443 reverse-RPC.",
444 SessionID
445 );
446
447 AdapterStdinSender = None;
448
449 AdapterChildPid = None;
450 },
451
452 _ => {
453 dev_log!(
454 "exthost",
455 "warn: [DebugProvider] Unknown adapter descriptor type '{}' for session '{}' - registering \
456 session without spawn.",
457 DescriptorType,
458 SessionID
459 );
460
461 AdapterStdinSender = None;
462
463 AdapterChildPid = None;
464 },
465 }
466
467 if let Err(RegError) = self.ApplicationState.Feature.Debug.RegisterDebugSession(
472 crate::ApplicationState::State::FeatureState::Debug::DebugState::DebugSessionEntry {
473 SessionId:SessionID.clone(),
474 DebugType:DebugType.clone(),
475 SideCarIdentifier:TargetSideCar.clone(),
476 StdinSender:AdapterStdinSender,
477 ChildPid:AdapterChildPid,
478 },
479 ) {
480 dev_log!(
481 "exthost",
482 "warn: [DebugProvider] Failed to register session '{}' in DebugState: {}",
483 SessionID,
484 RegError
485 );
486 }
487
488 let StartedMethod = format!("{}$onDidStartDebugSession", ProxyTarget::ExtHostDebug.GetTargetPrefix());
498
499 let StartedSession = json!({
500 "id": SessionID.clone(),
501 "type": DebugType.clone(),
502 "name": ResolvedConfig.get("name").and_then(Value::as_str).unwrap_or(&DebugType),
503 "configuration": ResolvedConfig.clone(),
504 });
505
506 if let Err(error) = IPCProvider
507 .SendNotificationToSideCar(TargetSideCar.clone(), StartedMethod, json!([StartedSession]))
508 .await
509 {
510 dev_log!(
511 "exthost",
512 "warn: [DebugProvider] StartDebugging notification failed for '{}': {:?}",
513 SessionID,
514 error
515 );
516 }
517
518 let _ = self.ApplicationHandle.emit(
524 "sky://debug/sessionStart",
525 json!({
526 "sessionId": SessionID.clone(),
527 "type": DebugType.clone(),
528 "configuration": ResolvedConfig.clone(),
529 }),
530 );
531
532 dev_log!("exthost", "[DebugProvider] Debug session '{}' started (simulation).", SessionID);
533
534 Ok(SessionID)
535 }
536
537 async fn SendCommand(&self, SessionID:String, Command:String, Arguments:Value) -> Result<Value, CommonError> {
538 dev_log!(
539 "exthost",
540 "[DebugProvider] SendCommand for session '{}' (command: '{}', args: {:?})",
541 SessionID,
542 Command,
543 Arguments
544 );
545
546 let SessionEntry = self.ApplicationState.Feature.Debug.GetDebugSession(&SessionID);
551
552 let RequestSeq = Arguments.get("seq").and_then(Value::as_u64).unwrap_or(0);
563
564 let RequestArguments = Arguments.get("arguments").cloned().unwrap_or(Arguments.clone());
565
566 let DapRequest = json!({
567 "seq": RequestSeq,
568 "type": "request",
569 "command": Command,
570 "arguments": RequestArguments,
571 });
572
573 if let Some(Entry) = SessionEntry.as_ref() {
574 if let Some(Sender) = Entry.StdinSender.as_ref() {
575 let Body = serde_json::to_vec(&DapRequest).map_err(|Error| {
576 CommonError::IPCError {
577 Description:format!("Failed to serialize DAP request for session {}: {}", SessionID, Error),
578 }
579 })?;
580
581 let Header = format!("Content-Length: {}\r\n\r\n", Body.len());
582
583 let mut Frame = Vec::with_capacity(Header.len() + Body.len());
584
585 Frame.extend_from_slice(Header.as_bytes());
586
587 Frame.extend_from_slice(&Body);
588
589 Sender.send(Frame).map_err(|Error| {
590 CommonError::IPCError {
591 Description:format!("Adapter stdin channel for session {} closed: {}", SessionID, Error),
592 }
593 })?;
594
595 return Ok(json!({
602 "success": true,
603 "sessionId": SessionID,
604 "command": Command,
605 "transport": "stdio",
606 }));
607 }
608 }
609
610 let TargetSidecar = SessionEntry
619 .as_ref()
620 .map(|E| E.SideCarIdentifier.clone())
621 .unwrap_or_else(|| "cocoon-main".to_string());
622
623 let SendDapMethod = format!("{}$sendDAPRequest", ProxyTarget::ExtHostDebug.GetTargetPrefix());
624
625 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
626
627 match IPCProvider
628 .SendRequestToSideCar(
629 TargetSidecar,
630 SendDapMethod,
631 json!([{ "sessionId": SessionID, "request": DapRequest }]),
632 15000,
633 )
634 .await
635 {
636 Ok(Response) => Ok(Response),
637
638 Err(Error) => {
639 dev_log!(
640 "exthost",
641 "warn: [DebugProvider] reverse-RPC SendCommand failed for session {}: {:?}",
642 SessionID,
643 Error
644 );
645
646 Err(Error)
647 },
648 }
649 }
650
651 async fn StopDebugging(&self, SessionID:String) -> Result<(), CommonError> {
652 dev_log!("exthost", "[DebugProvider] StopDebugging request for session '{}'", SessionID);
653
654 if let Some(Entry) = self.ApplicationState.Feature.Debug.GetDebugSession(&SessionID) {
659 if let Some(Sender) = Entry.StdinSender.as_ref() {
660 let DisconnectRequest = json!({
661 "seq": 0,
662 "type": "request",
663 "command": "disconnect",
664 "arguments": { "restart": false, "terminateDebuggee": true },
665 });
666
667 if let Ok(Body) = serde_json::to_vec(&DisconnectRequest) {
668 let Header = format!("Content-Length: {}\r\n\r\n", Body.len());
669
670 let mut Frame = Vec::with_capacity(Header.len() + Body.len());
671
672 Frame.extend_from_slice(Header.as_bytes());
673
674 Frame.extend_from_slice(&Body);
675
676 let _ = Sender.send(Frame);
677 }
678 }
679 }
680
681 let _ = self.ApplicationState.Feature.Debug.UnregisterDebugSession(&SessionID);
686
687 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
688
689 let TerminateMethod = format!("{}$onDidTerminateDebugSession", ProxyTarget::ExtHostDebug.GetTargetPrefix());
690
691 if let Err(error) = IPCProvider
692 .SendNotificationToSideCar("cocoon-main".to_string(), TerminateMethod, json!([{ "id": SessionID.clone() }]))
693 .await
694 {
695 dev_log!(
696 "exthost",
697 "warn: [DebugProvider] StopDebugging notification failed for '{}': {:?}",
698 SessionID,
699 error
700 );
701 }
702
703 let _ = self
704 .ApplicationHandle
705 .emit("sky://debug/sessionEnd", json!({ "sessionId": SessionID.clone() }));
706
707 Ok(())
708 }
709}