Skip to main content

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 serde_json::{Value, json};
16use tauri::{Emitter, Manager};
17use url::Url;
18
19use crate::{
20	ApplicationState::DTO::DocumentStateDTO::DocumentStateDTO,
21	Environment::Utility,
22	RunTime::ApplicationRunTime::ApplicationRunTime,
23	dev_log,
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	dev_log!("model", "[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		dev_log!("model", "[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					dev_log!(
55						"model",
56						"error: [DocumentProvider] Failed to emit document open event: {}",
57						error
58					);
59				}
60			},
61			Err(error) => {
62				dev_log!(
63					"model",
64					"error: [DocumentProvider] Failed to serialize existing document DTO: {}",
65					error
66				);
67			},
68		}
69
70		return Ok(existing_document.URI.clone());
71	}
72
73	// Resolve the content based on the URI scheme.
74	let file_content = if let Some(c) = content {
75		c
76	} else if uri.scheme() == "file" {
77		let file_path = uri.to_file_path().map_err(|_| {
78			CommonError::InvalidArgument {
79				ArgumentName:"URI".into(),
80				Reason:"Cannot convert non-file URI to path".into(),
81			}
82		})?;
83
84		let runtime = environment.ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
85
86		let file_content_bytes = runtime.Run(ReadFile(file_path.clone())).await?;
87
88		String::from_utf8(file_content_bytes)
89			.map_err(|error| CommonError::FileSystemIO { Path:file_path, Description:error.to_string() })?
90	} else {
91		// Custom scheme: attempt to resolve from a sidecar provider.
92		dev_log!(
93			"model",
94			"[DocumentProvider] Non-native scheme '{}'. Attempting to resolve from sidecar.",
95			uri.scheme()
96		);
97
98		let ipc_provider:Arc<dyn IPCProvider> = environment.Require();
99
100		let rpc_result = ipc_provider
101			.SendRequestToSideCar(
102				// In a multi-host world, we'd look this up
103				"cocoon-main".to_string(),
104				"$provideTextDocumentContent".to_string(),
105				json!([uri_components_dto]),
106				10000,
107			)
108			.await?;
109
110		rpc_result.as_str().map(String::from).ok_or_else(|| {
111			CommonError::IPCError {
112				Description:format!("Failed to get valid string content for custom URI scheme '{}'", uri.scheme()),
113			}
114		})?
115	};
116
117	// The rest of the flow is the same for all schemes.
118	let new_document = DocumentStateDTO::Create(uri.clone(), language_identifier, file_content)?;
119
120	let dto_for_notification = new_document.ToDTO()?;
121
122	environment
123		.ApplicationState
124		.Feature
125		.Documents
126		.OpenDocuments
127		.lock()
128		.map_err(Utility::MapApplicationStateLockErrorToCommonError)?
129		.insert(uri.to_string(), new_document);
130
131	if let Err(error) = environment
132		.ApplicationHandle
133		.emit("sky://documents/open", dto_for_notification.clone())
134	{
135		dev_log!(
136			"model",
137			"error: [DocumentProvider] Failed to emit document open event: {}",
138			error
139		);
140	}
141
142	crate::Environment::DocumentProvider::Notifications::notify_model_added(environment, &dto_for_notification).await;
143
144	Ok(uri)
145}