Mountain/Environment/
CustomEditorProvider.rs

1//! # CustomEditorProvider (Environment)
2//!
3//! RESPONSIBILITIES:
4//! - Implements
5//!   [`CustomEditorProvider`](CommonLibrary::CustomEditor::CustomEditorProvider)
6//!   for [`MountainEnvironment`]
7//! - Manages registration and lifecycle of custom non-text editors
8//! - Coordinates Webview-based editing experiences (SVG editors, diff viewers,
9//!   etc.)
10//! - Handles editor resolution, save operations, and provider unregistration
11//!
12//! ARCHITECTURAL ROLE:
13//! - Environment provider that enables extension-contributed custom editors
14//! - Uses [`IPCProvider`](CommonLibrary::IPC::IPCProvider) for RPC
15//!   communication with Cocoon
16//! - Integrates with
17//!   [`ApplicationState`](crate::ApplicationState::ApplicationState) for
18//!   provider registration persistence
19//!
20//! ERROR HANDLING:
21//! - Uses [`CommonError`](CommonLibrary::Error::CommonError) for all operations
22//! - ViewType validation: rejects empty view types with InvalidArgument error
23//! - Some operations are stubbed with logging/warning (OnSaveCustomDocument)
24//!
25//! PERFORMANCE:
26//! - Provider registration lookup should be O(1) via hash map in
27//!   ApplicationState (TODO)
28//! - ResolveCustomEditor uses fire-and-forget RPC pattern to avoid waiting
29//!
30//! VS CODE REFERENCE:
31//! - `vs/workbench/contrib/customEditor/browser/customEditorService.ts` -
32//!   custom editor service
33//! - `vs/workbench/contrib/customEditor/common/customEditor.ts` - custom editor
34//!   interfaces
35//! - `vs/platform/workspace/common/workspace.ts` - resource URI handling
36//!
37//! TODO:
38//! - Store provider registrations in ApplicationState with capability metadata
39//! - Implement custom editor backup/restore mechanism
40//! - Add support for multiple active instances of the same viewType
41//! - Implement custom editor move and rename handling
42//! - Add proper validation of viewType and resource URI
43//! - Implement editor-specific command registration
44//! - Add support for custom editor dispose/cleanup
45//! - Consider adding editor state persistence across reloads
46//! - Implement proper error recovery for Webview crashes
47//! - Add telemetry for custom editor usage metrics
48//!
49//! MODULE CONTENTS:
50//! - [`CustomEditorProvider`](CommonLibrary::CustomEditor::CustomEditorProvider) implementation:
51//! - `RegisterCustomEditorProvider` - register extension provider
52//! - `UnregisterCustomEditorProvider` - unregister provider
53//! - `OnSaveCustomDocument` - save handler (stub)
54//! - `ResolveCustomEditor` - resolve editor content via RPC
55
56use std::sync::Arc;
57
58use CommonLibrary::{
59	CustomEditor::CustomEditorProvider::CustomEditorProvider,
60	Environment::Requires::Requires,
61	Error::CommonError::CommonError,
62	IPC::{DTO::ProxyTarget::ProxyTarget, IPCProvider::IPCProvider},
63};
64use async_trait::async_trait;
65use log::{info, warn};
66use serde_json::{Value, json};
67use url::Url;
68
69use super::MountainEnvironment::MountainEnvironment;
70
71#[async_trait]
72impl CustomEditorProvider for MountainEnvironment {
73	async fn RegisterCustomEditorProvider(&self, ViewType:String, _Options:Value) -> Result<(), CommonError> {
74		info!("[CustomEditorProvider] Registering provider for view type: {}", ViewType);
75
76		// Validate ViewType is non-empty
77		if ViewType.is_empty() {
78			return Err(CommonError::InvalidArgument {
79				ArgumentName:"ViewType".to_string(),
80				Reason:"ViewType cannot be empty".to_string(),
81			});
82		}
83
84		// Register custom editor provider in ApplicationState for lifecycle management
85		// and resolution. Should associate ViewType with the sidecar identifier for
86		// RPC routing, store provider capabilities (supportsMultipleEditors,
87		// serialization support), store custom options (mime types, file extensions),
88		// validate that the ViewType is not already registered to prevent conflicts,
89		// and track registration timestamp and extension origin for debugging.
90
91		Ok(())
92	}
93
94	async fn UnregisterCustomEditorProvider(&self, ViewType:String) -> Result<(), CommonError> {
95		info!("[CustomEditorProvider] Unregistering provider for view type: {}", ViewType);
96
97		// Remove custom editor provider registration from ApplicationState. Should
98		// check if any active editors are currently using this ViewType and either
99		// force close with unsaved changes warning or prevent unregistration, remove
100		// all stored configuration, capabilities, and sidecar association, notify the
101		// sidecar extension to clean up its internal state, and remove any cached
102		// resolution entries for this ViewType.
103
104		Ok(())
105	}
106
107	async fn OnSaveCustomDocument(&self, ViewType:String, ResourceURI:Url) -> Result<(), CommonError> {
108		info!(
109			"[CustomEditorProvider] OnSaveCustomDocument called for '{}' at '{}'",
110			ViewType, ResourceURI
111		);
112
113		// Implement the complete custom document save workflow. Send RPC request
114		// ($customDocument/save) to the extension sidecar responsible for this
115		// ViewType, including the ResourceURI. The extension retrieves edited content
116		// from its Webview via postMessage and returns the updated data (string or
117		// bytes). Mountain receives the content and writes it to the file system using
118		// FileSystemWriter with appropriate encoding and atomic write pattern. Emit
119		// a document saved notification to refresh UI and trigger post-save hooks
120		// (formatters, linters, extension notifications). This enables custom editors
121		// to participate in the standard save lifecycle.
122
123		warn!("[CustomEditorProvider] OnSaveCustomDocument is not fully implemented.");
124		Ok(())
125	}
126
127	async fn ResolveCustomEditor(
128		&self,
129		ViewType:String,
130		ResourceURI:Url,
131		WebviewPanelHandle:String,
132	) -> Result<(), CommonError> {
133		info!(
134			"[CustomEditorProvider] Resolving custom editor for '{}' on resource '{}'",
135			ViewType, ResourceURI
136		);
137
138		// This is the core logic:
139		// 1. Find the sidecar that registered this ViewType. For now, assume
140		//    "cocoon-main".
141		// 2. Make an RPC call to that sidecar's implementation of
142		//    `$resolveCustomEditor`.
143		// 3. The sidecar will then call back to the host with `setHtml`, `postMessage`,
144		//    etc. to populate the webview associated with the `WebviewPanelHandle`.
145
146		let IPCProvider:Arc<dyn IPCProvider> = self.Require();
147		let ResourceURIComponents = json!({ "external": ResourceURI.to_string() });
148		let RPCMethod = format!("{}$resolveCustomEditor", ProxyTarget::ExtHostCustomEditors.GetTargetPrefix());
149		let RPCParameters = json!([ResourceURIComponents, ViewType, WebviewPanelHandle]);
150
151		// This is a fire-and-forget notification. The sidecar is expected to
152		// call back to the host to populate the webview.
153		IPCProvider
154			.SendNotificationToSideCar("cocoon-main".to_string(), RPCMethod, RPCParameters)
155			.await
156	}
157}