Skip to main content

Mountain/IPC/WindServiceHandlers/
Model.rs

1#![allow(non_snake_case, unused_variables, dead_code, unused_imports)]
2
3//! Text model registry and TextFile handlers - open, close, get, update,
4//! read, write, save.
5
6use std::sync::Arc;
7
8use serde_json::{Value, json};
9
10use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, dev_log};
11
12// ============================================================================
13// Model (Text Model Registry) Handlers
14// ============================================================================
15
16/// Open a text model: read content from disk and register in DocumentState.
17/// Returns { uri, content, version, languageId }.
18pub async fn ModelOpen(RunTime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
19	let Uri = Arguments
20		.first()
21		.and_then(|V| V.as_str())
22		.ok_or("model:open requires uri".to_string())?
23		.to_owned();
24
25	// Derive file path from URI
26	let FilePath = if Uri.starts_with("file://") {
27		Uri.trim_start_matches("file://").to_owned()
28	} else {
29		Uri.clone()
30	};
31
32	// Read file content from disk
33	let Content = tokio::fs::read_to_string(&FilePath).await.unwrap_or_default();
34
35	// Detect language from extension
36	let LanguageId = std::path::Path::new(&FilePath)
37		.extension()
38		.and_then(|E| E.to_str())
39		.map(|Ext| {
40			match Ext {
41				"rs" => "rust",
42				"ts" | "tsx" => "typescript",
43				"js" | "jsx" | "mjs" | "cjs" => "javascript",
44				"json" | "jsonc" => "json",
45				"toml" => "toml",
46				"yaml" | "yml" => "yaml",
47				"md" => "markdown",
48				"html" | "htm" => "html",
49				"css" | "scss" | "less" => "css",
50				"sh" | "bash" | "zsh" => "shellscript",
51				"py" => "python",
52				"go" => "go",
53				"c" | "h" => "c",
54				"cpp" | "cc" | "cxx" | "hpp" => "cpp",
55				_ => "plaintext",
56			}
57		})
58		.unwrap_or("plaintext")
59		.to_owned();
60
61	// Determine next version (1 if new, increment if exists)
62	let Version = RunTime
63		.Environment
64		.ApplicationState
65		.Feature
66		.Documents
67		.Get(&Uri)
68		.map(|D| D.Version + 1)
69		.unwrap_or(1);
70
71	// Register in document state
72	{
73		use crate::ApplicationState::DTO::DocumentStateDTO::DocumentStateDTO;
74
75		if let Ok(ParsedUri) = url::Url::parse(&Uri) {
76			let Lines:Vec<String> = Content.lines().map(|L| L.to_owned()).collect();
77			let Eol = if Content.contains("\r\n") { "\r\n" } else { "\n" }.to_owned();
78
79			let Document = DocumentStateDTO {
80				URI:ParsedUri,
81				LanguageIdentifier:LanguageId.clone(),
82				Version,
83				Lines,
84				EOL:Eol,
85				IsDirty:false,
86				Encoding:"utf-8".to_owned(),
87				VersionIdentifier:Version,
88			};
89
90			RunTime
91				.Environment
92				.ApplicationState
93				.Feature
94				.Documents
95				.AddOrUpdate(Uri.clone(), Document);
96		}
97	}
98
99	Ok(json!({
100		"uri": Uri,
101		"content": Content,
102		"version": Version,
103		"languageId": LanguageId,
104	}))
105}
106
107/// Close a text model and remove it from DocumentState.
108pub async fn ModelClose(RunTime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
109	let Uri = Arguments
110		.first()
111		.and_then(|V| V.as_str())
112		.ok_or("model:close requires uri".to_string())?;
113
114	RunTime.Environment.ApplicationState.Feature.Documents.Remove(Uri);
115	Ok(Value::Null)
116}
117
118/// Get the current snapshot of an open text model, or null if not open.
119pub async fn ModelGet(RunTime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
120	let Uri = Arguments
121		.first()
122		.and_then(|V| V.as_str())
123		.ok_or("model:get requires uri".to_string())?;
124
125	match RunTime.Environment.ApplicationState.Feature.Documents.Get(Uri) {
126		None => Ok(Value::Null),
127		Some(Document) => {
128			Ok(json!({
129				"uri": Uri,
130				"content": Document.Lines.join(&Document.EOL),
131				"version": Document.Version,
132				"languageId": Document.LanguageIdentifier,
133			}))
134		},
135	}
136}
137
138/// Return all currently open text models.
139pub async fn ModelGetAll(RunTime:Arc<ApplicationRunTime>) -> Result<Value, String> {
140	let All = RunTime
141		.Environment
142		.ApplicationState
143		.Feature
144		.Documents
145		.GetAll()
146		.into_iter()
147		.map(|(Uri, Document)| {
148			json!({
149				"uri": Uri,
150				"content": Document.Lines.join(&Document.EOL),
151				"version": Document.Version,
152				"languageId": Document.LanguageIdentifier,
153			})
154		})
155		.collect::<Vec<_>>();
156
157	Ok(Value::Array(All))
158}
159
160/// Update the content of an open text model, incrementing its version.
161pub async fn ModelUpdateContent(RunTime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
162	let Uri = Arguments
163		.first()
164		.and_then(|V| V.as_str())
165		.ok_or("model:updateContent requires uri".to_string())?
166		.to_owned();
167
168	let NewContent = Arguments
169		.get(1)
170		.and_then(|V| V.as_str())
171		.ok_or("model:updateContent requires content".to_string())?
172		.to_owned();
173
174	let (NewVersion, LanguageId) = match RunTime.Environment.ApplicationState.Feature.Documents.Get(&Uri) {
175		None => return Err(format!("model:updateContent - model not open: {}", Uri)),
176		Some(mut Document) => {
177			Document.Version += 1;
178			Document.Lines = NewContent.lines().map(|L| L.to_owned()).collect();
179			Document.IsDirty = true;
180			let Version = Document.Version;
181			let LangId = Document.LanguageIdentifier.clone();
182			RunTime
183				.Environment
184				.ApplicationState
185				.Feature
186				.Documents
187				.AddOrUpdate(Uri.clone(), Document);
188			(Version, LangId)
189		},
190	};
191
192	Ok(json!({
193		"uri": Uri,
194		"content": NewContent,
195		"version": NewVersion,
196		"languageId": LanguageId,
197	}))
198}
199
200// ============================================================================
201// TextFile Handlers
202// ============================================================================
203
204/// Read a text file from disk.
205pub async fn TextfileRead(_runtime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
206	let Path = Arguments
207		.first()
208		.and_then(|V| V.as_str())
209		.ok_or_else(|| "textFile:read requires path as first argument".to_string())?;
210
211	tokio::fs::read_to_string(Path)
212		.await
213		.map(Value::String)
214		.map_err(|Error| format!("textFile:read failed: {}", Error))
215}
216
217/// Write text to a file on disk.
218pub async fn TextfileWrite(_runtime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
219	let Path = Arguments
220		.first()
221		.and_then(|V| V.as_str())
222		.ok_or_else(|| "textFile:write requires path as first argument".to_string())?;
223	let Content = Arguments.get(1).and_then(|V| V.as_str()).unwrap_or("").to_string();
224
225	tokio::fs::write(Path, Content.as_bytes())
226		.await
227		.map(|()| Value::Null)
228		.map_err(|Error| format!("textFile:write failed: {}", Error))
229}
230
231/// Save a document - forward save intent to Sky frontend.
232pub async fn TextfileSave(_runtime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
233	// Actual disk write happens via textFile:write; this is a UI-dirty-state hint.
234	let _Uri = Arguments.first().and_then(|V| V.as_str()).unwrap_or("").to_string();
235	dev_log!("vfs", "textFile:save uri={:?}", _Uri);
236	Ok(Value::Null)
237}