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, SkyEvent::SkyEvent},
14};
15use serde_json::{Value, json};
16// `Emitter` was previously imported here for the now-replaced
17// direct `.emit(...)` calls; emit is now done via `LogSkyEmit`
18// which carries the trait import internally. `Manager` remains
19// because `.state::<…>()` below depends on it.
20use tauri::Manager;
21use url::Url;
22
23use crate::{
24	ApplicationState::DTO::DocumentStateDTO::DocumentStateDTO,
25	Environment::Utility,
26	IPC::SkyEmit::LogSkyEmit,
27	RunTime::ApplicationRunTime::ApplicationRunTime,
28	dev_log,
29};
30
31/// Opens a document. If the URI scheme is not native (`file`), it attempts to
32/// resolve the content from a registered sidecar provider
33/// (`TextDocumentContentProvider`).
34pub(super) async fn open_document(
35	environment:&crate::Environment::MountainEnvironment::MountainEnvironment,
36	uri_components_dto:Value,
37	language_identifier:Option<String>,
38	content:Option<String>,
39) -> Result<Url, CommonError> {
40	let uri = Utility::UriParsing::GetURLFromURIComponentsDTO(&uri_components_dto)?;
41
42	dev_log!("model", "[DocumentProvider] Opening document: {}", uri);
43
44	// First, check if the document is already open.
45	if let Some(existing_document) = environment
46		.ApplicationState
47		.Feature
48		.Documents
49		.OpenDocuments
50		.lock()
51		.map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
52		.get(uri.as_str())
53	{
54		dev_log!("model", "[DocumentProvider] Document {} is already open.", uri);
55
56		match existing_document.ToDTO() {
57			Ok(dto) => {
58				if let Err(error) = LogSkyEmit(&environment.ApplicationHandle, SkyEvent::DocumentsOpen.AsStr(), dto) {
59					dev_log!(
60						"model",
61						"error: [DocumentProvider] Failed to emit document open event: {}",
62						error
63					);
64				}
65			},
66			Err(error) => {
67				dev_log!(
68					"model",
69					"error: [DocumentProvider] Failed to serialize existing document DTO: {}",
70					error
71				);
72			},
73		}
74
75		return Ok(existing_document.URI.clone());
76	}
77
78	// Resolve the content based on the URI scheme.
79	let file_content = if let Some(c) = content {
80		c
81	} else if uri.scheme() == "file" {
82		let file_path = uri.to_file_path().map_err(|_| {
83			CommonError::InvalidArgument {
84				ArgumentName:"URI".into(),
85				Reason:"Cannot convert non-file URI to path".into(),
86			}
87		})?;
88
89		let runtime = environment.ApplicationHandle.state::<Arc<ApplicationRunTime>>().inner().clone();
90
91		let file_content_bytes = runtime.Run(ReadFile(file_path.clone())).await?;
92
93		String::from_utf8(file_content_bytes)
94			.map_err(|error| CommonError::FileSystemIO { Path:file_path, Description:error.to_string() })?
95	} else {
96		// Custom scheme: attempt to resolve from a sidecar provider.
97		dev_log!(
98			"model",
99			"[DocumentProvider] Non-native scheme '{}'. Attempting to resolve from sidecar.",
100			uri.scheme()
101		);
102
103		let ipc_provider:Arc<dyn IPCProvider> = environment.Require();
104
105		let rpc_result = ipc_provider
106			.SendRequestToSideCar(
107				// In a multi-host world, we'd look this up
108				"cocoon-main".to_string(),
109				"$provideTextDocumentContent".to_string(),
110				json!([uri_components_dto]),
111				10000,
112			)
113			.await?;
114
115		rpc_result.as_str().map(String::from).ok_or_else(|| {
116			CommonError::IPCError {
117				Description:format!("Failed to get valid string content for custom URI scheme '{}'", uri.scheme()),
118			}
119		})?
120	};
121
122	// The rest of the flow is the same for all schemes.
123	let new_document = DocumentStateDTO::Create(uri.clone(), language_identifier, file_content)?;
124
125	let dto_for_notification = new_document.ToDTO()?;
126
127	environment
128		.ApplicationState
129		.Feature
130		.Documents
131		.OpenDocuments
132		.lock()
133		.map_err(Utility::ErrorMapping::MapApplicationStateLockErrorToCommonError)?
134		.insert(uri.to_string(), new_document);
135
136	if let Err(error) = LogSkyEmit(
137		&environment.ApplicationHandle,
138		SkyEvent::DocumentsOpen.AsStr(),
139		dto_for_notification.clone(),
140	) {
141		dev_log!(
142			"model",
143			"error: [DocumentProvider] Failed to emit document open event: {}",
144			error
145		);
146	}
147
148	crate::Environment::DocumentProvider::Notifications::notify_model_added(environment, &dto_for_notification).await;
149
150	Ok(uri)
151}