Mountain/Environment/DebugProvider.rs
1//! # DebugProvider (Environment)
2//!
3//! RESPONSIBILITIES:
4//! - Implements [`DebugService`](CommonLibrary::Debug::DebugService) for
5//! [`MountainEnvironment`]
6//! - Manages complete debugging session lifecycle from configuration to
7//! termination
8//! - Orchestrates between extension host (Cocoon), debug adapter, and UI
9//! - Handles DAP (Debug Adapter Protocol) message mediation
10//!
11//! ARCHITECTURAL ROLE:
12//! - Core provider for debugging functionality, analogous to VSCode's debug
13//! service
14//! - Uses two-stage registration: configuration providers and adapter
15//! descriptor factories
16//! - Each debug type (node, java, rust) can have its own configuration and
17//! adapter
18//! - Integrates with [`IPCProvider`](CommonLibrary::IPC::IPCProvider) for RPC
19//! to Cocoon
20//!
21//! DEBUG SESSION FLOW:
22//! 1. UI calls `StartDebugging` with folder URI and configuration
23//! 2. Mountain RPCs to Cocoon to resolve debug configuration (variable
24//! substitution)
25//! 3. Mountain RPCs to Cocoon to create debug adapter descriptor
26//! 4. Mountain spawns debug adapter process or connects to TCP server
27//! 5. Mountain mediates DAP messages between UI and debug adapter
28//! 6. UI sends DAP commands via `SendCommand` which forwards to adapter
29//! 7. Debug adapter sends DAP events/notifications back through Mountain to UI
30//! 8. Session ends on stop request or adapter process exit
31//!
32//! ERROR HANDLING:
33//! - Uses [`CommonError`](CommonLibrary::Error::CommonError) for all operations
34//! - Validates debug type is non-empty (InvalidArgument error)
35//! - TODO: Implement proper session lookup, timeout handling, and error
36//! recovery
37//!
38//! PERFORMANCE:
39//! - Debug adapter spawning should be async with timeout protection (5000ms in
40//! current RPC)
41//! - DAP message routing needs efficient session lookup (TODO: O(1) hash map)
42//! - Multiple simultaneous debug sessions require careful resource management
43//!
44//! VS CODE REFERENCE:
45//! - `vs/workbench/contrib/debug/browser/debugService.ts` - debug service main
46//! logic
47//! - `vs/workbench/contrib/debug/common/debug.ts` - debug interfaces and models
48//! - `vs/workbench/contrib/debug/browser/adapter/descriptorFactory.ts` -
49//! adapter descriptor factories
50//! - `vs/debugAdapter/common/debugProtocol.ts` - DAP protocol specification
51//!
52//! TODO:
53//! - Store debug adapter registrations in ApplicationState
54//! - Implement proper debug session tracking and management
55//! - Add debug adapter process spawning and lifecycle management
56//! - Implement proper DAP message routing and serialization
57//! - Add debug session state persistence across UI reloads
58//! - Implement debug console and variable inspection integration
59//! - Add support for multiple simultaneous debug sessions
60//! - Implement debug adapter termination and cleanup
61//! - Add debug session metrics and telemetry
62//! - Consider implementing debug configuration validation
63//! - Add support for debug adapters that communicate via TCP sockets
64//! - Implement debug adapter crash detection and recovery
65//!
66//! MODULE CONTENTS:
67//! - [`DebugService`](CommonLibrary::Debug::DebugService) implementation:
68//! - `RegisterDebugConfigurationProvider` - register config resolver
69//! - `RegisterDebugAdapterDescriptorFactory` - register adapter factory
70//! - `StartDebugging` - start debug session (partial)
71//! - `SendCommand` - send DAP command to adapter (stub)
72
73use std::sync::Arc;
74
75use CommonLibrary::{
76 Debug::DebugService::DebugService,
77 Environment::Requires::Requires,
78 Error::CommonError::CommonError,
79 IPC::{DTO::ProxyTarget::ProxyTarget, IPCProvider::IPCProvider},
80};
81use async_trait::async_trait;
82use serde_json::{Value, json};
83use url::Url;
84
85use super::MountainEnvironment::MountainEnvironment;
86use crate::dev_log;
87
88#[async_trait]
89impl DebugService for MountainEnvironment {
90 async fn RegisterDebugConfigurationProvider(
91 &self,
92
93 DebugType:String,
94
95 ProviderHandle:u32,
96
97 SideCarIdentifier:String,
98 ) -> Result<(), CommonError> {
99 // Validate debug type is non-empty
100 if DebugType.is_empty() {
101 return Err(CommonError::InvalidArgument {
102 ArgumentName:"DebugType".to_string(),
103 Reason:"DebugType cannot be empty".to_string(),
104 });
105 }
106
107 dev_log!(
108 "exthost",
109 "[DebugProvider] Registering DebugConfigurationProvider for type '{}' (handle: {}, sidecar: {})",
110 DebugType,
111 ProviderHandle,
112 SideCarIdentifier
113 );
114
115 // Store debug configuration provider registration in ApplicationState
116 self.ApplicationState
117 .Feature
118 .Debug
119 .RegisterDebugConfigurationProvider(DebugType, ProviderHandle, SideCarIdentifier)
120 .map_err(|e| CommonError::Unknown { Description:e })?;
121
122 Ok(())
123 }
124
125 async fn RegisterDebugAdapterDescriptorFactory(
126 &self,
127
128 DebugType:String,
129
130 FactoryHandle:u32,
131
132 SideCarIdentifier:String,
133 ) -> Result<(), CommonError> {
134 // Validate debug type is non-empty
135 if DebugType.is_empty() {
136 return Err(CommonError::InvalidArgument {
137 ArgumentName:"DebugType".to_string(),
138 Reason:"DebugType cannot be empty".to_string(),
139 });
140 }
141
142 dev_log!(
143 "exthost",
144 "[DebugProvider] Registering DebugAdapterDescriptorFactory for type '{}' (handle: {}, sidecar: {})",
145 DebugType,
146 FactoryHandle,
147 SideCarIdentifier
148 );
149
150 // Store debug adapter descriptor factory registration in ApplicationState
151 self.ApplicationState
152 .Feature
153 .Debug
154 .RegisterDebugAdapterDescriptorFactory(DebugType, FactoryHandle, SideCarIdentifier)
155 .map_err(|e| CommonError::Unknown { Description:e })?;
156
157 Ok(())
158 }
159
160 async fn StartDebugging(&self, _FolderURI:Option<Url>, Configuration:Value) -> Result<String, CommonError> {
161 let SessionID = uuid::Uuid::new_v4().to_string();
162 dev_log!(
163 "exthost",
164 "[DebugProvider] Starting debug session '{}' with config: {:?}",
165 SessionID,
166 Configuration
167 );
168
169 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
170 let DebugType = Configuration
171 .get("type")
172 .and_then(Value::as_str)
173 .ok_or_else(|| {
174 CommonError::InvalidArgument {
175 ArgumentName:"Configuration".into(),
176
177 Reason:"Missing 'type' field in debug configuration.".into(),
178 }
179 })?
180 .to_string();
181
182 // TODO: Look up which sidecar (extension) handles this debug type using
183 // the registration stored in ApplicationState. The mapping should be based
184 // on previous RegisterDebugConfigurationProvider calls. Initial stub uses
185 // hardcoded "cocoon-main" until proper registration tracking is implemented.
186 let TargetSideCar = "cocoon-main".to_string();
187
188 // 1. Resolve configuration (Reverse-RPC to Cocoon)
189 dev_log!(
190 "exthost",
191 "[DebugProvider] Resolving debug configuration for type '{}'",
192 DebugType
193 );
194 dev_log!("exthost", "[DebugProvider] Resolving debug configuration...");
195 let ResolveConfigMethod = format!("{}$resolveDebugConfiguration", ProxyTarget::ExtHostDebug.GetTargetPrefix());
196 let ResolvedConfig = IPCProvider
197 .SendRequestToSideCar(
198 TargetSideCar.clone(),
199 ResolveConfigMethod,
200 json!([DebugType.clone(), Configuration]),
201 5000,
202 )
203 .await?;
204
205 // 2. Get the Debug Adapter Descriptor (Reverse-RPC to Cocoon)
206 dev_log!("exthost", "[DebugProvider] Creating debug adapter descriptor...");
207 let CreateDescriptorMethod =
208 format!("{}$createDebugAdapterDescriptor", ProxyTarget::ExtHostDebug.GetTargetPrefix());
209 let Descriptor = IPCProvider
210 .SendRequestToSideCar(
211 TargetSideCar.clone(),
212 CreateDescriptorMethod,
213 json!([DebugType, &ResolvedConfig]),
214 5000,
215 )
216 .await?;
217
218 // 3. Spawn the Debug Adapter process based on the descriptor.
219 dev_log!(
220 "exthost",
221 "[DebugProvider] Spawning Debug Adapter based on descriptor: {:?}",
222 Descriptor
223 );
224
225 // TODO: Implement full debug adapter spawning based on the descriptor.
226 // A complete implementation would:
227 // - Parse the DebugAdapterDescriptor (executable path, command args,
228 // environment variables, or server port for TCP connection)
229 // - Spawn a new OS process with stdio pipes using Command or connect to a TCP
230 // socket if using debug adapter server mode
231 // - Create a new DebugSession struct to manage the DAP (Debug Adapter Protocol)
232 // communication stream, handling JSON-RPC message framing
233 // - Establish bidirectional JSON-RPC communication with the debug adapter
234 // - Store the active session in ApplicationState keyed by session_id for later
235 // command routing and session management
236 // - Implement proper session cleanup on termination (kill process, close
237 // sockets, remove from ApplicationState, emit exit events)
238 // - Handle adapter launch failures with descriptive error messages and proper
239 // session state cleanup
240
241 dev_log!("exthost", "[DebugProvider] Debug session '{}' started (simulation).", SessionID);
242 Ok(SessionID)
243 }
244
245 async fn SendCommand(&self, SessionID:String, Command:String, Arguments:Value) -> Result<Value, CommonError> {
246 dev_log!(
247 "exthost",
248 "[DebugProvider] SendCommand for session '{}' (command: '{}', args: {:?})",
249 SessionID,
250 Command,
251 Arguments
252 );
253
254 // TODO: Implement proper debug session management to route commands to
255 // active debug adapters. Should:
256 // - Look up session by SessionID in ApplicationState's debug session registry
257 // - Validate session exists and is in active state (not terminated or crashed)
258 // - Serialize command and arguments to JSON-RPC 2.0 format with proper request
259 // sequencing (seq number)
260 // - Send the request to debug adapter via stdio pipes or TCP socket
261 // - Wait for response with appropriate timeout, handle cancellation requests
262 // - Deserialize JSON-RPC response and return the result body to the caller
263 // - Handle timeouts, adapter crashes, and protocol errors gracefully with
264 // informative error messages and session cleanup as needed
265
266 // For now, return a placeholder response indicating debug session is active
267 let response = serde_json::json!({
268 "success": true,
269 "session_id": SessionID,
270 "command": Command,
271 "response": {
272 "type": "response",
273 "request_seq": 1,
274 "success": true,
275 "command": Command,
276 "body": {}
277 }
278 });
279
280 Ok(response)
281 }
282}