1#![allow(non_snake_case, non_camel_case_types)]
17
18use std::{path::PathBuf, sync::Arc};
19
20use Common::{
21 Document::DocumentProvider::DocumentProvider,
22 Effect::ApplicationRunTime::ApplicationRunTime as _,
23 Environment::Requires::Requires,
24 Error::CommonError::CommonError,
25 FileSystem::{ReadFile::ReadFile, WriteFileBytes::WriteFileBytes},
26 IPC::IPCProvider::IPCProvider,
27 UserInterface::{DTO::SaveDialogOptionsDTO::SaveDialogOptionsDTO, ShowSaveDialog::ShowSaveDialog},
28};
29use async_trait::async_trait;
30use log::{error, info, trace, warn};
31use serde_json::{Value, json};
32use tauri::{Emitter, Manager};
33use url::Url;
34
35use super::{MountainEnvironment::MountainEnvironment, Utility};
36use crate::{
37 ApplicationState::DTO::DocumentStateDTO::DocumentStateDTO,
38 RunTime::ApplicationRunTime::ApplicationRunTime,
39};
40
41#[async_trait]
42impl DocumentProvider for MountainEnvironment {
43 async fn OpenDocument(
47 &self,
48
49 URIComponentsDTO:Value,
50
51 LanguageIdentifier:Option<String>,
52
53 Content:Option<String>,
54 ) -> Result<Url, CommonError> {
55 let URI = Utility::GetURLFromURIComponentsDTO(&URIComponentsDTO)?;
56
57 info!("[DocumentProvider] Opening document: {}", URI);
58
59 if let Some(ExistingDocument) = self
61 .ApplicationState
62 .OpenDocuments
63 .lock()
64 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?
65 .get(URI.as_str())
66 {
67 info!("[DocumentProvider] Document {} is already open.", URI);
68
69 match ExistingDocument.ToDTO() {
70 Ok(DTO) => {
71 if let Err(Error) = self.ApplicationHandle.emit("sky://documents/open", DTO) {
72 error!("[DocumentProvider] Failed to emit document open event: {}", Error);
73 }
74 },
75
76 Err(Error) => {
77 error!("[DocumentProvider] Failed to serialize existing document DTO: {}", Error);
78 },
79 }
80
81 return Ok(ExistingDocument.URI.clone());
82 }
83
84 let FileContent = if let Some(c) = Content {
86 c
87 } else if URI.scheme() == "file" {
88 let FilePath = URI.to_file_path().map_err(|_| {
89 CommonError::InvalidArgument {
90 ArgumentName:"URI".into(),
91
92 Reason:"Cannot convert non-file URI to path".into(),
93 }
94 })?;
95
96 let RunTime = self.ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
97
98 let FileContentBytes = RunTime.Run(ReadFile(FilePath.clone())).await?;
99
100 String::from_utf8(FileContentBytes)
101 .map_err(|Error| CommonError::FileSystemIO { Path:FilePath, Description:Error.to_string() })?
102 } else {
103 info!(
105 "[DocumentProvider] Non-native scheme '{}'. Attempting to resolve from sidecar.",
106 URI.scheme()
107 );
108
109 let IPCProvider:Arc<dyn IPCProvider> = self.Require();
110
111 let RpcResult = IPCProvider
112 .SendRequestToSideCar(
113 "cocoon-main".to_string(),
115 "$provideTextDocumentContent".to_string(),
116 json!([URIComponentsDTO]),
117 10000,
118 )
119 .await?;
120
121 RpcResult.as_str().map(String::from).ok_or_else(|| {
122 CommonError::IPCError {
123 Description:format!("Failed to get valid string content for custom URI scheme '{}'", URI.scheme()),
124 }
125 })?
126 };
127
128 let NewDocument = DocumentStateDTO::Create(URI.clone(), LanguageIdentifier, FileContent);
130
131 let DTOForNotification = NewDocument.ToDTO()?;
132
133 self.ApplicationState
134 .OpenDocuments
135 .lock()
136 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?
137 .insert(URI.to_string(), NewDocument);
138
139 if let Err(Error) = self.ApplicationHandle.emit("sky://documents/open", DTOForNotification.clone()) {
140 error!("[DocumentProvider] Failed to emit document open event: {}", Error);
141 }
142
143 NotifyModelAdded(self, &DTOForNotification).await;
144
145 Ok(URI)
146 }
147
148 async fn SaveDocument(&self, URI:Url) -> Result<bool, CommonError> {
150 info!("[DocumentProvider] Saving document: {}", URI);
151
152 let (ContentBytes, FilePath) = {
153 let mut OpenDocumentsGuard = self
154 .ApplicationState
155 .OpenDocuments
156 .lock()
157 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
158
159 if let Some(Document) = OpenDocumentsGuard.get_mut(URI.as_str()) {
160 if URI.scheme() != "file" {
161 return Err(CommonError::NotImplemented {
162 FeatureName:format!(
163 "Saving for URI scheme '{}' is not supported via this method.",
164 URI.scheme()
165 ),
166 });
167 }
168
169 Document.IsDirty = false;
170
171 (
172 Document.GetText().into_bytes(),
173 URI.to_file_path().map_err(|_| {
174 CommonError::InvalidArgument {
175 ArgumentName:"URI".into(),
176
177 Reason:"Cannot convert file URI to path".into(),
178 }
179 })?,
180 )
181 } else {
182 return Err(CommonError::FileSystemNotFound(URI.to_file_path().unwrap_or_default()));
183 }
184 };
185
186 let RunTime = self.ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
187
188 RunTime.Run(WriteFileBytes(FilePath, ContentBytes, true, true)).await?;
189
190 if let Err(Error) = self
191 .ApplicationHandle
192 .emit("sky://documents/saved", json!({ "uri": URI.to_string() }))
193 {
194 error!("[DocumentProvider] Failed to emit document saved event: {}", Error);
195 }
196
197 NotifyModelSaved(self, &URI).await;
198
199 Ok(true)
200 }
201
202 async fn SaveDocumentAs(&self, OriginalURI:Url, NewTargetURI:Option<Url>) -> Result<Option<Url>, CommonError> {
204 info!("[DocumentProvider] Saving document as: {}", OriginalURI);
205
206 let RunTime = self.ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
207
208 let NewFilePath = match NewTargetURI {
209 Some(uri) => uri.to_file_path().ok(),
210
211 None => RunTime.Run(ShowSaveDialog(Some(SaveDialogOptionsDTO::default()))).await?,
212 };
213
214 let Some(NewPath) = NewFilePath else { return Ok(None) };
215
216 let NewURI = Url::from_file_path(&NewPath).map_err(|_| {
217 CommonError::InvalidArgument {
218 ArgumentName:"NewPath".into(),
219
220 Reason:"Could not convert new path to URI".into(),
221 }
222 })?;
223
224 let OriginalContent = {
225 let Guard = self
226 .ApplicationState
227 .OpenDocuments
228 .lock()
229 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
230
231 Guard
232 .get(OriginalURI.as_str())
233 .map(|doc| doc.GetText())
234 .ok_or_else(|| CommonError::FileSystemNotFound(PathBuf::from(OriginalURI.path())))?
235 };
236
237 RunTime
238 .Run(WriteFileBytes(NewPath, OriginalContent.clone().into_bytes(), true, true))
239 .await?;
240
241 let NewDocumentState = {
242 let mut Guard = self
243 .ApplicationState
244 .OpenDocuments
245 .lock()
246 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
247
248 let OldDocument = Guard.remove(OriginalURI.as_str());
249
250 let NewDocument =
251 DocumentStateDTO::Create(NewURI.clone(), OldDocument.map(|d| d.LanguageIdentifier), OriginalContent);
252
253 let DTO = NewDocument.ToDTO()?;
254
255 Guard.insert(NewURI.to_string(), NewDocument);
256
257 DTO
258 };
259
260 NotifyModelRemoved(self, &OriginalURI).await;
261
262 NotifyModelAdded(self, &NewDocumentState).await;
263
264 if let Err(Error) = self.ApplicationHandle.emit(
265 "sky://documents/renamed",
266 json!({ "oldUri": OriginalURI.to_string(), "newUri": NewURI.to_string() }),
267 ) {
268 error!("[DocumentProvider] Failed to emit document renamed event: {}", Error);
269 }
270
271 Ok(Some(NewURI))
272 }
273
274 async fn SaveAllDocuments(&self, _IncludeUntitled:bool) -> Result<Vec<bool>, CommonError> {
276 warn!("[DocumentProvider] SaveAllDocuments is not implemented.");
277
278 Err(CommonError::NotImplemented { FeatureName:"SaveAllDocuments".into() })
279 }
280
281 async fn ApplyDocumentChanges(
283 &self,
284
285 URI:Url,
286
287 NewVersionIdentifier:i64,
288
289 ChangesDTOCollection:Value,
290
291 _IsDirtyAfterChange:bool,
292
293 _IsUndoing:bool,
294
295 _IsRedoing:bool,
296 ) -> Result<(), CommonError> {
297 trace!("[DocumentProvider] Applying changes to document: {}", URI);
298
299 {
300 let mut OpenDocumentsGuard = self
301 .ApplicationState
302 .OpenDocuments
303 .lock()
304 .map_err(Utility::MapApplicationStateLockErrorToCommonError)?;
305
306 if let Some(Document) = OpenDocumentsGuard.get_mut(URI.as_str()) {
307 Document.ApplyChanges(NewVersionIdentifier, &ChangesDTOCollection)?;
308 } else {
309 warn!("[DocumentProvider] Received changes for unknown document: {}", URI);
310
311 return Ok(());
312 }
313 }
314
315 NotifyModelChanged(self, &URI, NewVersionIdentifier, ChangesDTOCollection).await;
316
317 Ok(())
318 }
319}
320
321async fn NotifyModelAdded(Environment:&MountainEnvironment, DocumentStateDTO:&Value) {
325 let URIString = DocumentStateDTO.get("URI").and_then(Value::as_str).unwrap_or("unknown");
326
327 info!("[DocumentProvider] Notifying ModelAdded for: {}", URIString);
328
329 let Payload = json!([DocumentStateDTO]);
330
331 let IPCProvider:Arc<dyn IPCProvider> = Environment.Require();
332
333 if let Err(Error) = IPCProvider
334 .SendNotificationToSideCar("cocoon-main".to_string(), "$acceptModelAdded".to_string(), Payload)
335 .await
336 {
337 error!(
338 "[DocumentProvider] Failed to send $acceptModelAdded for {}: {}",
339 URIString, Error
340 );
341 }
342}
343
344async fn NotifyModelChanged(Environment:&MountainEnvironment, URI:&Url, NewVersion:i64, Changes:Value) {
346 info!("[DocumentProvider] Notifying ModelChanged for: {}", URI);
347
348 let URIComponents = json!({ "external": URI.to_string(), "$mid": 1 });
349
350 let EventData = json!({ "versionId": NewVersion, "changes": Changes, "isDirty": true });
351
352 let Payload = json!([URIComponents, EventData]);
353
354 let IPCProvider:Arc<dyn IPCProvider> = Environment.Require();
355
356 if let Err(Error) = IPCProvider
357 .SendNotificationToSideCar("cocoon-main".to_string(), "$acceptModelChanged".to_string(), Payload)
358 .await
359 {
360 error!("[DocumentProvider] Failed to send $acceptModelChanged for {}: {}", URI, Error);
361 }
362}
363
364async fn NotifyModelSaved(Environment:&MountainEnvironment, URI:&Url) {
366 info!("[DocumentProvider] Notifying ModelSaved for: {}", URI);
367
368 let URIComponents = json!({ "external": URI.to_string(), "$mid": 1 });
369
370 let Payload = json!([URIComponents]);
371
372 let IPCProvider:Arc<dyn IPCProvider> = Environment.Require();
373
374 if let Err(Error) = IPCProvider
375 .SendNotificationToSideCar("cocoon-main".to_string(), "$acceptModelSaved".to_string(), Payload)
376 .await
377 {
378 error!("[DocumentProvider] Failed to send $acceptModelSaved for {}: {}", URI, Error);
379 }
380}
381
382pub async fn NotifyModelRemoved(Environment:&MountainEnvironment, URI:&Url) {
384 info!("[DocumentProvider] Notifying ModelRemoved for: {}", URI);
385
386 let URIComponents = json!({ "external": URI.to_string(), "$mid": 1 });
387
388 let Payload = json!([URIComponents]);
389
390 let IPCProvider:Arc<dyn IPCProvider> = Environment.Require();
391
392 if let Err(Error) = IPCProvider
393 .SendNotificationToSideCar("cocoon-main".to_string(), "$acceptModelRemoved".to_string(), Payload)
394 .await
395 {
396 error!("[DocumentProvider] Failed to send $acceptModelRemoved for {}: {}", URI, Error);
397 }
398}