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}