Skip to main content

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}