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 = 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" => {
433 let Port = Descriptor.get("port").and_then(Value::as_u64).ok_or_else(|| {
438 CommonError::InvalidArgument {
439 ArgumentName:"Descriptor.port".into(),
440 Reason:"server adapter descriptor missing 'port'".into(),
441 }
442 })? as u16;
443
444 let Host = Descriptor
445 .get("host")
446 .and_then(Value::as_str)
447 .unwrap_or("127.0.0.1")
448 .to_string();
449
450 let Addr = format!("{}:{}", Host, Port);
451
452 dev_log!(
453 "exthost",
454 "[DebugProvider] Connecting to debug adapter server at {} (session '{}')",
455 Addr,
456 SessionID
457 );
458
459 let TcpStream = tokio::net::TcpStream::connect(&Addr).await.map_err(|Error| {
460 CommonError::IPCError {
461 Description:format!(
462 "Failed to connect to debug adapter server at {} for session {}: {}",
463 Addr, SessionID, Error
464 ),
465 }
466 })?;
467
468 let (ReadHalf, WriteHalf) = tokio::io::split(TcpStream);
469
470 let (Sender, mut Receiver) = tokio::sync::mpsc::unbounded_channel::<Vec<u8>>();
471
472 let WriterSessionId = SessionID.clone();
474 tokio::spawn(async move {
475 use tokio::io::AsyncWriteExt;
476 let mut Pipe = WriteHalf;
477 while let Some(Frame) = Receiver.recv().await {
478 if let Err(Error) = Pipe.write_all(&Frame).await {
479 crate::dev_log!(
480 "exthost",
481 "warn: [DebugAdapter/server] write failed for session {}: {}",
482 WriterSessionId,
483 Error
484 );
485 break;
486 }
487 let _ = Pipe.flush().await;
488 }
489 });
490
491 let ReaderSessionId = SessionID.clone();
494 let ReaderHandle = self.ApplicationHandle.clone();
495 let ReaderSidecar = TargetSideCar.clone();
496 tokio::spawn(async move {
497 use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
498 let mut Reader = BufReader::new(ReadHalf);
499 let mut Header = String::new();
500 loop {
501 Header.clear();
502 let mut ContentLength:usize = 0;
503 loop {
504 Header.clear();
505 match Reader.read_line(&mut Header).await {
506 Ok(0) => return,
507 Ok(_) => {},
508 Err(Error) => {
509 crate::dev_log!(
510 "exthost",
511 "warn: [DebugAdapter/server] read failed for session {}: {}",
512 ReaderSessionId,
513 Error
514 );
515 return;
516 },
517 }
518 let Trimmed = Header.trim_end_matches("\r\n").trim_end_matches('\n');
519 if Trimmed.is_empty() {
520 break;
521 }
522 if let Some(Rest) = Trimmed.strip_prefix("Content-Length:") {
523 if let Ok(N) = Rest.trim().parse::<usize>() {
524 ContentLength = N;
525 }
526 }
527 }
528 if ContentLength == 0 {
529 continue;
530 }
531 let mut Body = vec![0u8; ContentLength];
532 if let Err(Error) = Reader.read_exact(&mut Body).await {
533 crate::dev_log!(
534 "exthost",
535 "warn: [DebugAdapter/server] body read failed for session {}: {}",
536 ReaderSessionId,
537 Error
538 );
539 return;
540 }
541 let Parsed:Value = serde_json::from_slice(&Body).unwrap_or(Value::Null);
542 let _ = ReaderHandle.emit(
543 "sky://debug/dap-message",
544 json!({
545 "sessionId": ReaderSessionId,
546 "sidecarId": ReaderSidecar,
547 "message": Parsed,
548 }),
549 );
550 }
551 });
552
553 AdapterStdinSender = Some(Sender);
554 AdapterChildPid = None;
555
556 dev_log!(
557 "exthost",
558 "[DebugProvider] Connected to server adapter at {} for session '{}'",
559 Addr,
560 SessionID
561 );
562 },
563
564 "pipeServer" => {
565 let PipePath = Descriptor
569 .get("path")
570 .and_then(Value::as_str)
571 .ok_or_else(|| {
572 CommonError::InvalidArgument {
573 ArgumentName:"Descriptor.path".into(),
574 Reason:"pipeServer adapter descriptor missing 'path'".into(),
575 }
576 })?
577 .to_string();
578
579 dev_log!(
580 "exthost",
581 "[DebugProvider] Connecting to debug adapter pipe at '{}' (session '{}')",
582 PipePath,
583 SessionID
584 );
585
586 #[cfg(unix)]
587 let (ReadHalf, WriteHalf) = {
588 let Stream = tokio::net::UnixStream::connect(&PipePath).await.map_err(|Error| {
589 CommonError::IPCError {
590 Description:format!(
591 "Failed to connect to debug adapter pipe '{}' for session {}: {}",
592 PipePath, SessionID, Error
593 ),
594 }
595 })?;
596 tokio::io::split(Stream)
597 };
598
599 #[cfg(windows)]
600 let (ReadHalf, WriteHalf) = {
601 let Stream =
604 tokio::net::windows::named_pipe::ClientOptions::new()
605 .open(&PipePath)
606 .map_err(|Error| {
607 CommonError::IPCError {
608 Description:format!(
609 "Failed to open named pipe '{}' for session {}: {}",
610 PipePath, SessionID, Error
611 ),
612 }
613 })?;
614 tokio::io::split(Stream)
615 };
616
617 let (Sender, mut Receiver) = tokio::sync::mpsc::unbounded_channel::<Vec<u8>>();
618
619 let PipeWriterSessionId = SessionID.clone();
620 tokio::spawn(async move {
621 use tokio::io::AsyncWriteExt;
622 let mut Pipe = WriteHalf;
623 while let Some(Frame) = Receiver.recv().await {
624 if let Err(Error) = Pipe.write_all(&Frame).await {
625 crate::dev_log!(
626 "exthost",
627 "warn: [DebugAdapter/pipe] write failed for session {}: {}",
628 PipeWriterSessionId,
629 Error
630 );
631 break;
632 }
633 let _ = Pipe.flush().await;
634 }
635 });
636
637 let PipeReaderSessionId = SessionID.clone();
638 let PipeReaderHandle = self.ApplicationHandle.clone();
639 let PipeReaderSidecar = TargetSideCar.clone();
640 tokio::spawn(async move {
641 use tokio::io::{AsyncBufReadExt, AsyncReadExt, BufReader};
642 let mut Reader = BufReader::new(ReadHalf);
643 let mut Header = String::new();
644 loop {
645 Header.clear();
646 let mut ContentLength:usize = 0;
647 loop {
648 Header.clear();
649 match Reader.read_line(&mut Header).await {
650 Ok(0) => return,
651 Ok(_) => {},
652 Err(Error) => {
653 crate::dev_log!(
654 "exthost",
655 "warn: [DebugAdapter/pipe] read failed for session {}: {}",
656 PipeReaderSessionId,
657 Error
658 );
659 return;
660 },
661 }
662 let Trimmed = Header.trim_end_matches("\r\n").trim_end_matches('\n');
663 if Trimmed.is_empty() {
664 break;
665 }
666 if let Some(Rest) = Trimmed.strip_prefix("Content-Length:") {
667 if let Ok(N) = Rest.trim().parse::<usize>() {
668 ContentLength = N;
669 }
670 }
671 }
672 if ContentLength == 0 {
673 continue;
674 }
675 let mut Body = vec![0u8; ContentLength];
676 if let Err(Error) = Reader.read_exact(&mut Body).await {
677 crate::dev_log!(
678 "exthost",
679 "warn: [DebugAdapter/pipe] body read failed for session {}: {}",
680 PipeReaderSessionId,
681 Error
682 );
683 return;
684 }
685 let Parsed:Value = serde_json::from_slice(&Body).unwrap_or(Value::Null);
686 let _ = PipeReaderHandle.emit(
687 "sky://debug/dap-message",
688 json!({
689 "sessionId": PipeReaderSessionId,
690 "sidecarId": PipeReaderSidecar,
691 "message": Parsed,
692 }),
693 );
694 }
695 });
696
697 AdapterStdinSender = Some(Sender);
698 AdapterChildPid = None;
699
700 dev_log!(
701 "exthost",
702 "[DebugProvider] Connected to pipe adapter at '{}' for session '{}'",
703 PipePath,
704 SessionID
705 );
706 },
707
708 "implementation" => {
709 dev_log!(
710 "exthost",
711 "[DebugProvider] Inline implementation adapter for session '{}' - DAP frames travel via Cocoon \
712 reverse-RPC.",
713 SessionID
714 );
715
716 AdapterStdinSender = None;
717
718 AdapterChildPid = None;
719 },
720
721 _ => {
722 dev_log!(
723 "exthost",
724 "warn: [DebugProvider] Unknown adapter descriptor type '{}' for session '{}' - registering \
725 session without spawn.",
726 DescriptorType,
727 SessionID
728 );
729
730 AdapterStdinSender = None;
731
732 AdapterChildPid = None;
733 },
734 }
735
736 if let Err(RegError) = self.ApplicationState.Feature.Debug.RegisterDebugSession(
741 crate::ApplicationState::State::FeatureState::Debug::DebugState::DebugSessionEntry {
742 SessionId:SessionID.clone(),
743 DebugType:DebugType.clone(),
744 SideCarIdentifier:TargetSideCar.clone(),
745 StdinSender:AdapterStdinSender,
746 ChildPid:AdapterChildPid,
747 },
748 ) {
749 dev_log!(
750 "exthost",
751 "warn: [DebugProvider] Failed to register session '{}' in DebugState: {}",
752 SessionID,
753 RegError
754 );
755 }
756
757 let StartedMethod = format!("{}$onDidStartDebugSession", ProxyTarget::ExtHostDebug.GetTargetPrefix());
767
768 let StartedSession = json!({
769 "id": SessionID.clone(),
770 "type": DebugType.clone(),
771 "name": ResolvedConfig.get("name").and_then(Value::as_str).unwrap_or(&DebugType),
772 "configuration": ResolvedConfig.clone(),
773 });
774
775 if let Err(error) = IPCProvider
776 .SendNotificationToSideCar(TargetSideCar.clone(), StartedMethod, json!([StartedSession]))
777 .await
778 {
779 dev_log!(
780 "exthost",
781 "warn: [DebugProvider] StartDebugging notification failed for '{}': {:?}",
782 SessionID,
783 error
784 );
785 }
786
787 let _ = self.ApplicationHandle.emit(
793 "sky://debug/sessionStart",
794 json!({
795 "sessionId": SessionID.clone(),
796 "type": DebugType.clone(),
797 "configuration": ResolvedConfig.clone(),
798 }),
799 );
800
801 dev_log!("exthost", "[DebugProvider] Debug session '{}' started (simulation).", SessionID);
802
803 Ok(SessionID)
804 }
805
806 async fn SendCommand(&self, SessionID:String, Command:String, Arguments:Value) -> Result<Value, CommonError> {
807 dev_log!(
808 "exthost",
809 "[DebugProvider] SendCommand for session '{}' (command: '{}', args: {:?})",
810 SessionID,
811 Command,
812 Arguments
813 );
814
815 let SessionEntry = self.ApplicationState.Feature.Debug.GetDebugSession(&SessionID);
820
821 let RequestSeq = Arguments.get("seq").and_then(Value::as_u64).unwrap_or(0);
832
833 let RequestArguments = Arguments.get("arguments").cloned().unwrap_or(Arguments.clone());
834
835 let DapRequest = json!({
836 "seq": RequestSeq,
837 "type": "request",
838 "command": Command,
839 "arguments": RequestArguments,
840 });
841
842 if let Some(Entry) = SessionEntry.as_ref() {
843 if let Some(Sender) = Entry.StdinSender.as_ref() {
844 let Body = serde_json::to_vec(&DapRequest).map_err(|Error| {
845 CommonError::IPCError {
846 Description:format!("Failed to serialize DAP request for session {}: {}", SessionID, Error),
847 }
848 })?;
849
850 let Header = format!("Content-Length: {}\r\n\r\n", Body.len());
851
852 let mut Frame = Vec::with_capacity(Header.len() + Body.len());
853
854 Frame.extend_from_slice(Header.as_bytes());
855
856 Frame.extend_from_slice(&Body);
857
858 Sender.send(Frame).map_err(|Error| {
859 CommonError::IPCError {
860 Description:format!("Adapter stdin channel for session {} closed: {}", SessionID, Error),
861 }
862 })?;
863
864 return Ok(json!({
871 "success": true,
872 "sessionId": SessionID,
873 "command": Command,
874 "transport": "stdio",
875 }));
876 }
877 }
878
879 let TargetSidecar = SessionEntry
888 .as_ref()
889 .map(|E| E.SideCarIdentifier.clone())
890 .unwrap_or_else(|| "cocoon-main".to_string());
891
892 let SendDapMethod = format!("{}$sendDAPRequest", ProxyTarget::ExtHostDebug.GetTargetPrefix());
893
894 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
895
896 match IPCProvider
897 .SendRequestToSideCar(
898 TargetSidecar,
899 SendDapMethod,
900 json!([{ "sessionId": SessionID, "request": DapRequest }]),
901 15000,
902 )
903 .await
904 {
905 Ok(Response) => Ok(Response),
906
907 Err(Error) => {
908 dev_log!(
909 "exthost",
910 "warn: [DebugProvider] reverse-RPC SendCommand failed for session {}: {:?}",
911 SessionID,
912 Error
913 );
914
915 Err(Error)
916 },
917 }
918 }
919
920 async fn StopDebugging(&self, SessionID:String) -> Result<(), CommonError> {
921 dev_log!("exthost", "[DebugProvider] StopDebugging request for session '{}'", SessionID);
922
923 if let Some(Entry) = self.ApplicationState.Feature.Debug.GetDebugSession(&SessionID) {
928 if let Some(Sender) = Entry.StdinSender.as_ref() {
929 let DisconnectRequest = json!({
930 "seq": 0,
931 "type": "request",
932 "command": "disconnect",
933 "arguments": { "restart": false, "terminateDebuggee": true },
934 });
935
936 if let Ok(Body) = serde_json::to_vec(&DisconnectRequest) {
937 let Header = format!("Content-Length: {}\r\n\r\n", Body.len());
938
939 let mut Frame = Vec::with_capacity(Header.len() + Body.len());
940
941 Frame.extend_from_slice(Header.as_bytes());
942
943 Frame.extend_from_slice(&Body);
944
945 let _ = Sender.send(Frame);
946 }
947 }
948 }
949
950 let _ = self.ApplicationState.Feature.Debug.UnregisterDebugSession(&SessionID);
955
956 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
957
958 let TerminateMethod = format!("{}$onDidTerminateDebugSession", ProxyTarget::ExtHostDebug.GetTargetPrefix());
959
960 if let Err(error) = IPCProvider
961 .SendNotificationToSideCar("cocoon-main".to_string(), TerminateMethod, json!([{ "id": SessionID.clone() }]))
962 .await
963 {
964 dev_log!(
965 "exthost",
966 "warn: [DebugProvider] StopDebugging notification failed for '{}': {:?}",
967 SessionID,
968 error
969 );
970 }
971
972 let _ = self
973 .ApplicationHandle
974 .emit("sky://debug/sessionEnd", json!({ "sessionId": SessionID.clone() }));
975
976 Ok(())
977 }
978}