Mountain/Environment/DocumentProvider/
OpenDocument.rs

1//! Document opening and content resolution logic.
2//!
3//! Handles opening documents from file:// URIs, custom scheme URIs (via sidecar
4//! providers), and already-open documents.
5
6use std::sync::Arc;
7
8use CommonLibrary::{
9	Effect::ApplicationRunTime::ApplicationRunTime as _,
10	Environment::Requires::Requires,
11	Error::CommonError::CommonError,
12	FileSystem::ReadFile::ReadFile,
13	IPC::IPCProvider::IPCProvider,
14};
15use log::{error, info};
16use serde_json::{Value, json};
17use tauri::{Emitter, Manager};
18use url::Url;
19
20use crate::{
21	ApplicationState::DTO::DocumentStateDTO::DocumentStateDTO,
22	Environment::Utility,
23	RunTime::ApplicationRunTime::ApplicationRunTime,
24};
25
26/// Opens a document. If the URI scheme is not native (`file`), it attempts to
27/// resolve the content from a registered sidecar provider
28/// (`TextDocumentContentProvider`).
29pub(super) async fn open_document(
30	environment:&crate::Environment::MountainEnvironment::MountainEnvironment,
31	uri_components_dto:Value,
32	language_identifier:Option<String>,
33	content:Option<String>,
34) -> Result<Url, CommonError> {
35	let uri = Utility::GetURLFromURIComponentsDTO(&uri_components_dto)?;
36
37	info!("[DocumentProvider] Opening document: {}", uri);
38
39	// First, check if the document is already open.
40	if let Some(existing_document) = environment
41		.ApplicationState
42		.Feature
43		.Documents
44		.OpenDocuments
45		.lock()
46		.map_err(Utility::MapApplicationStateLockErrorToCommonError)?
47		.get(uri.as_str())
48	{
49		info!("[DocumentProvider] Document {} is already open.", uri);
50
51		match existing_document.ToDTO() {
52			Ok(dto) => {
53				if let Err(error) = environment.ApplicationHandle.emit("sky://documents/open", dto) {
54					error!("[DocumentProvider] Failed to emit document open event: {}", error);
55				}
56			},
57			Err(error) => {
58				error!("[DocumentProvider] Failed to serialize existing document DTO: {}", error);
59			},
60		}
61
62		return Ok(existing_document.URI.clone());
63	}
64
65	// Resolve the content based on the URI scheme.
66	let file_content = if let Some(c) = content {
67		c
68	} else if uri.scheme() == "file" {
69		let file_path = uri.to_file_path().map_err(|_| {
70			CommonError::InvalidArgument {
71				ArgumentName:"URI".into(),
72				Reason:"Cannot convert non-file URI to path".into(),
73			}
74		})?;
75
76		let runtime = environment.ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
77
78		let file_content_bytes = runtime.Run(ReadFile(file_path.clone())).await?;
79
80		String::from_utf8(file_content_bytes)
81			.map_err(|error| CommonError::FileSystemIO { Path:file_path, Description:error.to_string() })?
82	} else {
83		// Custom scheme: attempt to resolve from a sidecar provider.
84		info!(
85			"[DocumentProvider] Non-native scheme '{}'. Attempting to resolve from sidecar.",
86			uri.scheme()
87		);
88
89		let ipc_provider:Arc<dyn IPCProvider> = environment.Require();
90
91		let rpc_result = ipc_provider
92			.SendRequestToSideCar(
93				// In a multi-host world, we'd look this up
94				"cocoon-main".to_string(),
95				"$provideTextDocumentContent".to_string(),
96				json!([uri_components_dto]),
97				10000,
98			)
99			.await?;
100
101		rpc_result.as_str().map(String::from).ok_or_else(|| {
102			CommonError::IPCError {
103				Description:format!("Failed to get valid string content for custom URI scheme '{}'", uri.scheme()),
104			}
105		})?
106	};
107
108	// The rest of the flow is the same for all schemes.
109	let new_document = DocumentStateDTO::Create(uri.clone(), language_identifier, file_content)?;
110
111	let dto_for_notification = new_document.ToDTO()?;
112
113	environment
114		.ApplicationState
115		.Feature
116		.Documents
117		.OpenDocuments
118		.lock()
119		.map_err(Utility::MapApplicationStateLockErrorToCommonError)?
120		.insert(uri.to_string(), new_document);
121
122	if let Err(error) = environment
123		.ApplicationHandle
124		.emit("sky://documents/open", dto_for_notification.clone())
125	{
126		error!("[DocumentProvider] Failed to emit document open event: {}", error);
127	}
128
129	crate::Environment::DocumentProvider::Notifications::notify_model_added(environment, &dto_for_notification).await;
130
131	Ok(uri)
132}