1use std::sync::Arc;
79
80use CommonLibrary::{
81 Debug::DebugService::DebugService,
82 Environment::Requires::Requires,
83 Error::CommonError::CommonError,
84 IPC::{DTO::ProxyTarget::ProxyTarget, IPCProvider::IPCProvider},
85};
86use async_trait::async_trait;
87use serde_json::{Value, json};
88use tauri::Emitter;
89use url::Url;
90
91use super::MountainEnvironment::MountainEnvironment;
92use crate::dev_log;
93
94#[async_trait]
95impl DebugService for MountainEnvironment {
96 async fn RegisterDebugConfigurationProvider(
97 &self,
98
99 DebugType:String,
100
101 ProviderHandle:u32,
102
103 SideCarIdentifier:String,
104 ) -> Result<(), CommonError> {
105 if DebugType.is_empty() {
107 return Err(CommonError::InvalidArgument {
108 ArgumentName:"DebugType".to_string(),
109 Reason:"DebugType cannot be empty".to_string(),
110 });
111 }
112
113 dev_log!(
114 "exthost",
115 "[DebugProvider] Registering DebugConfigurationProvider for type '{}' (handle: {}, sidecar: {})",
116 DebugType,
117 ProviderHandle,
118 SideCarIdentifier
119 );
120
121 self.ApplicationState
123 .Feature
124 .Debug
125 .RegisterDebugConfigurationProvider(DebugType, ProviderHandle, SideCarIdentifier)
126 .map_err(|e| CommonError::Unknown { Description:e })?;
127
128 Ok(())
129 }
130
131 async fn RegisterDebugAdapterDescriptorFactory(
132 &self,
133
134 DebugType:String,
135
136 FactoryHandle:u32,
137
138 SideCarIdentifier:String,
139 ) -> Result<(), CommonError> {
140 if DebugType.is_empty() {
142 return Err(CommonError::InvalidArgument {
143 ArgumentName:"DebugType".to_string(),
144 Reason:"DebugType cannot be empty".to_string(),
145 });
146 }
147
148 dev_log!(
149 "exthost",
150 "[DebugProvider] Registering DebugAdapterDescriptorFactory for type '{}' (handle: {}, sidecar: {})",
151 DebugType,
152 FactoryHandle,
153 SideCarIdentifier
154 );
155
156 self.ApplicationState
158 .Feature
159 .Debug
160 .RegisterDebugAdapterDescriptorFactory(DebugType, FactoryHandle, SideCarIdentifier)
161 .map_err(|e| CommonError::Unknown { Description:e })?;
162
163 Ok(())
164 }
165
166 async fn StartDebugging(&self, _FolderURI:Option<Url>, Configuration:Value) -> Result<String, CommonError> {
167 let SessionID = uuid::Uuid::new_v4().to_string();
168 dev_log!(
169 "exthost",
170 "[DebugProvider] Starting debug session '{}' with config: {:?}",
171 SessionID,
172 Configuration
173 );
174
175 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
176 let DebugType = Configuration
177 .get("type")
178 .and_then(Value::as_str)
179 .ok_or_else(|| {
180 CommonError::InvalidArgument {
181 ArgumentName:"Configuration".into(),
182
183 Reason:"Missing 'type' field in debug configuration.".into(),
184 }
185 })?
186 .to_string();
187
188 let TargetSideCar = "cocoon-main".to_string();
193
194 dev_log!(
196 "exthost",
197 "[DebugProvider] Resolving debug configuration for type '{}'",
198 DebugType
199 );
200 dev_log!("exthost", "[DebugProvider] Resolving debug configuration...");
201 let ResolveConfigMethod = format!("{}$resolveDebugConfiguration", ProxyTarget::ExtHostDebug.GetTargetPrefix());
202 let ResolvedConfig = IPCProvider
203 .SendRequestToSideCar(
204 TargetSideCar.clone(),
205 ResolveConfigMethod,
206 json!([DebugType.clone(), Configuration]),
207 5000,
208 )
209 .await?;
210
211 dev_log!("exthost", "[DebugProvider] Creating debug adapter descriptor...");
213 let CreateDescriptorMethod =
214 format!("{}$createDebugAdapterDescriptor", ProxyTarget::ExtHostDebug.GetTargetPrefix());
215 let Descriptor = IPCProvider
216 .SendRequestToSideCar(
217 TargetSideCar.clone(),
218 CreateDescriptorMethod,
219 json!([DebugType, &ResolvedConfig]),
220 5000,
221 )
222 .await?;
223
224 dev_log!(
226 "exthost",
227 "[DebugProvider] Spawning Debug Adapter based on descriptor: {:?}",
228 Descriptor
229 );
230
231 let DescriptorType = Descriptor.get("type").and_then(Value::as_str).unwrap_or("").to_string();
252 let AdapterStdinSender:Option<tokio::sync::mpsc::UnboundedSender<Vec<u8>>>;
253 let AdapterChildPid:Option<u32>;
254 match DescriptorType.as_str() {
255 "executable" => {
256 let Command = Descriptor
257 .get("command")
258 .and_then(Value::as_str)
259 .ok_or_else(|| {
260 CommonError::InvalidArgument {
261 ArgumentName:"Descriptor.command".into(),
262 Reason:"executable adapter descriptor missing 'command'".into(),
263 }
264 })?
265 .to_string();
266 let Args:Vec<String> = Descriptor
267 .get("args")
268 .and_then(Value::as_array)
269 .map(|A| A.iter().filter_map(|V| V.as_str().map(str::to_string)).collect())
270 .unwrap_or_default();
271 let OptionsValue = Descriptor.get("options").cloned().unwrap_or(Value::Null);
272 let Cwd = OptionsValue.get("cwd").and_then(Value::as_str).map(str::to_string);
273 let EnvOverrides:Vec<(String, String)> = OptionsValue
274 .get("env")
275 .and_then(Value::as_object)
276 .map(|O| {
277 O.iter()
278 .filter_map(|(K, V)| V.as_str().map(|S| (K.clone(), S.to_string())))
279 .collect()
280 })
281 .unwrap_or_default();
282
283 let mut Builder = tokio::process::Command::new(&Command);
284 Builder
285 .args(&Args)
286 .stdin(std::process::Stdio::piped())
287 .stdout(std::process::Stdio::piped())
288 .stderr(std::process::Stdio::piped());
289 if let Some(CwdPath) = &Cwd {
290 Builder.current_dir(CwdPath);
291 }
292 for (Key, Value) in &EnvOverrides {
293 Builder.env(Key, Value);
294 }
295
296 let mut Child = Builder.spawn().map_err(|Error| {
297 CommonError::IPCError {
298 Description:format!(
299 "Failed to spawn debug adapter '{}' for session {}: {}",
300 Command, SessionID, Error
301 ),
302 }
303 })?;
304
305 let Pid = Child.id();
306 let Stdin = Child.stdin.take().ok_or_else(|| {
307 CommonError::IPCError { Description:format!("Adapter for session {} had no stdin pipe", SessionID) }
308 })?;
309 let Stdout = Child.stdout.take().ok_or_else(|| {
310 CommonError::IPCError {
311 Description:format!("Adapter for session {} had no stdout pipe", SessionID),
312 }
313 })?;
314 let Stderr = Child.stderr.take().ok_or_else(|| {
315 CommonError::IPCError {
316 Description:format!("Adapter for session {} had no stderr pipe", SessionID),
317 }
318 })?;
319
320 let (Sender, mut Receiver) = tokio::sync::mpsc::unbounded_channel::<Vec<u8>>();
321
322 let StdinSessionId = SessionID.clone();
327 tokio::spawn(async move {
328 use tokio::io::AsyncWriteExt;
329 let mut Pipe = Stdin;
330 while let Some(Frame) = Receiver.recv().await {
331 if let Err(Error) = Pipe.write_all(&Frame).await {
332 crate::dev_log!(
333 "exthost",
334 "warn: [DebugAdapter] stdin write failed for session {}: {}",
335 StdinSessionId,
336 Error
337 );
338 break;
339 }
340 if let Err(Error) = Pipe.flush().await {
341 crate::dev_log!(
342 "exthost",
343 "warn: [DebugAdapter] stdin flush failed for session {}: {}",
344 StdinSessionId,
345 Error
346 );
347 break;
348 }
349 }
350 let _ = Pipe.shutdown().await;
351 });
352
353 let StdoutSessionId = SessionID.clone();
360 let StdoutHandle = self.ApplicationHandle.clone();
361 let StdoutSidecar = TargetSideCar.clone();
362 tokio::spawn(async move {
363 use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
364 let mut Reader = BufReader::new(Stdout);
365 let mut Header = String::new();
366 loop {
367 Header.clear();
368 let mut ContentLength:usize = 0;
369 loop {
370 Header.clear();
371 match Reader.read_line(&mut Header).await {
372 Ok(0) => return, Ok(_) => {},
374 Err(Error) => {
375 crate::dev_log!(
376 "exthost",
377 "warn: [DebugAdapter] stdout read failed for session {}: {}",
378 StdoutSessionId,
379 Error
380 );
381 return;
382 },
383 }
384 let Trimmed = Header.trim_end_matches("\r\n").trim_end_matches('\n');
385 if Trimmed.is_empty() {
386 break;
387 }
388 if let Some(Rest) = Trimmed.strip_prefix("Content-Length:") {
389 if let Ok(N) = Rest.trim().parse::<usize>() {
390 ContentLength = N;
391 }
392 }
393 }
394 if ContentLength == 0 {
395 continue;
396 }
397 let mut Body = vec![0u8; ContentLength];
398 if let Err(Error) = Reader.read_exact(&mut Body).await {
399 crate::dev_log!(
400 "exthost",
401 "warn: [DebugAdapter] stdout body read failed for session {}: {}",
402 StdoutSessionId,
403 Error
404 );
405 return;
406 }
407 let Parsed:Value = serde_json::from_slice(&Body).unwrap_or(Value::Null);
408 let _ = StdoutHandle.emit(
409 "sky://debug/dap-message",
410 json!({
411 "sessionId": StdoutSessionId,
412 "sidecarId": StdoutSidecar,
413 "message": Parsed,
414 }),
415 );
416 }
417 });
418
419 let StderrSessionId = SessionID.clone();
423 tokio::spawn(async move {
424 use tokio::io::{AsyncBufReadExt, BufReader};
425 let mut Lines = BufReader::new(Stderr).lines();
426 while let Ok(Some(Line)) = Lines.next_line().await {
427 crate::dev_log!("exthost", "[DebugAdapter] stderr session={}: {}", StderrSessionId, Line);
428 }
429 });
430
431 AdapterStdinSender = Some(Sender);
432 AdapterChildPid = Pid;
433 dev_log!(
434 "exthost",
435 "[DebugProvider] Spawned executable adapter for session '{}' pid={:?} command={:?}",
436 SessionID,
437 Pid,
438 Command
439 );
440 },
441 "server" | "pipeServer" => {
442 dev_log!(
443 "exthost",
444 "warn: [DebugProvider] Adapter type '{}' not yet wired (session '{}'). Reverse-RPC dispatch only.",
445 DescriptorType,
446 SessionID
447 );
448 AdapterStdinSender = None;
449 AdapterChildPid = None;
450 },
451 "implementation" => {
452 dev_log!(
453 "exthost",
454 "[DebugProvider] Inline implementation adapter for session '{}' - DAP frames travel via Cocoon \
455 reverse-RPC.",
456 SessionID
457 );
458 AdapterStdinSender = None;
459 AdapterChildPid = None;
460 },
461 _ => {
462 dev_log!(
463 "exthost",
464 "warn: [DebugProvider] Unknown adapter descriptor type '{}' for session '{}' - registering \
465 session without spawn.",
466 DescriptorType,
467 SessionID
468 );
469 AdapterStdinSender = None;
470 AdapterChildPid = None;
471 },
472 }
473
474 if let Err(RegError) = self.ApplicationState.Feature.Debug.RegisterDebugSession(
479 crate::ApplicationState::State::FeatureState::Debug::DebugState::DebugSessionEntry {
480 SessionId:SessionID.clone(),
481 DebugType:DebugType.clone(),
482 SideCarIdentifier:TargetSideCar.clone(),
483 StdinSender:AdapterStdinSender,
484 ChildPid:AdapterChildPid,
485 },
486 ) {
487 dev_log!(
488 "exthost",
489 "warn: [DebugProvider] Failed to register session '{}' in DebugState: {}",
490 SessionID,
491 RegError
492 );
493 }
494
495 let StartedMethod = format!("{}$onDidStartDebugSession", ProxyTarget::ExtHostDebug.GetTargetPrefix());
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 if let Err(error) = IPCProvider
512 .SendNotificationToSideCar(TargetSideCar.clone(), StartedMethod, json!([StartedSession]))
513 .await
514 {
515 dev_log!(
516 "exthost",
517 "warn: [DebugProvider] StartDebugging notification failed for '{}': {:?}",
518 SessionID,
519 error
520 );
521 }
522
523 let _ = self.ApplicationHandle.emit(
529 "sky://debug/sessionStart",
530 json!({
531 "sessionId": SessionID.clone(),
532 "type": DebugType.clone(),
533 "configuration": ResolvedConfig.clone(),
534 }),
535 );
536
537 dev_log!("exthost", "[DebugProvider] Debug session '{}' started (simulation).", SessionID);
538 Ok(SessionID)
539 }
540
541 async fn SendCommand(&self, SessionID:String, Command:String, Arguments:Value) -> Result<Value, CommonError> {
542 dev_log!(
543 "exthost",
544 "[DebugProvider] SendCommand for session '{}' (command: '{}', args: {:?})",
545 SessionID,
546 Command,
547 Arguments
548 );
549
550 let SessionEntry = self.ApplicationState.Feature.Debug.GetDebugSession(&SessionID);
555
556 let RequestSeq = Arguments.get("seq").and_then(Value::as_u64).unwrap_or(0);
567 let RequestArguments = Arguments.get("arguments").cloned().unwrap_or(Arguments.clone());
568 let DapRequest = json!({
569 "seq": RequestSeq,
570 "type": "request",
571 "command": Command,
572 "arguments": RequestArguments,
573 });
574
575 if let Some(Entry) = SessionEntry.as_ref() {
576 if let Some(Sender) = Entry.StdinSender.as_ref() {
577 let Body = serde_json::to_vec(&DapRequest).map_err(|Error| {
578 CommonError::IPCError {
579 Description:format!("Failed to serialize DAP request for session {}: {}", SessionID, Error),
580 }
581 })?;
582 let Header = format!("Content-Length: {}\r\n\r\n", Body.len());
583 let mut Frame = Vec::with_capacity(Header.len() + Body.len());
584 Frame.extend_from_slice(Header.as_bytes());
585 Frame.extend_from_slice(&Body);
586 Sender.send(Frame).map_err(|Error| {
587 CommonError::IPCError {
588 Description:format!("Adapter stdin channel for session {} closed: {}", SessionID, Error),
589 }
590 })?;
591 return Ok(json!({
598 "success": true,
599 "sessionId": SessionID,
600 "command": Command,
601 "transport": "stdio",
602 }));
603 }
604 }
605
606 let TargetSidecar = SessionEntry
615 .as_ref()
616 .map(|E| E.SideCarIdentifier.clone())
617 .unwrap_or_else(|| "cocoon-main".to_string());
618 let SendDapMethod = format!("{}$sendDAPRequest", ProxyTarget::ExtHostDebug.GetTargetPrefix());
619 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
620 match IPCProvider
621 .SendRequestToSideCar(
622 TargetSidecar,
623 SendDapMethod,
624 json!([{ "sessionId": SessionID, "request": DapRequest }]),
625 15000,
626 )
627 .await
628 {
629 Ok(Response) => Ok(Response),
630 Err(Error) => {
631 dev_log!(
632 "exthost",
633 "warn: [DebugProvider] reverse-RPC SendCommand failed for session {}: {:?}",
634 SessionID,
635 Error
636 );
637 Err(Error)
638 },
639 }
640 }
641
642 async fn StopDebugging(&self, SessionID:String) -> Result<(), CommonError> {
643 dev_log!("exthost", "[DebugProvider] StopDebugging request for session '{}'", SessionID);
644
645 if let Some(Entry) = self.ApplicationState.Feature.Debug.GetDebugSession(&SessionID) {
650 if let Some(Sender) = Entry.StdinSender.as_ref() {
651 let DisconnectRequest = json!({
652 "seq": 0,
653 "type": "request",
654 "command": "disconnect",
655 "arguments": { "restart": false, "terminateDebuggee": true },
656 });
657 if let Ok(Body) = serde_json::to_vec(&DisconnectRequest) {
658 let Header = format!("Content-Length: {}\r\n\r\n", Body.len());
659 let mut Frame = Vec::with_capacity(Header.len() + Body.len());
660 Frame.extend_from_slice(Header.as_bytes());
661 Frame.extend_from_slice(&Body);
662 let _ = Sender.send(Frame);
663 }
664 }
665 }
666 let _ = self.ApplicationState.Feature.Debug.UnregisterDebugSession(&SessionID);
671
672 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
673 let TerminateMethod = format!("{}$onDidTerminateDebugSession", ProxyTarget::ExtHostDebug.GetTargetPrefix());
674 if let Err(error) = IPCProvider
675 .SendNotificationToSideCar("cocoon-main".to_string(), TerminateMethod, json!([{ "id": SessionID.clone() }]))
676 .await
677 {
678 dev_log!(
679 "exthost",
680 "warn: [DebugProvider] StopDebugging notification failed for '{}': {:?}",
681 SessionID,
682 error
683 );
684 }
685 let _ = self
686 .ApplicationHandle
687 .emit("sky://debug/sessionEnd", json!({ "sessionId": SessionID.clone() }));
688 Ok(())
689 }
690}