Skip to main content

Mountain/IPC/WindServiceHandlers/Model/
ModelOpen.rs

1
2//! Open a text model: read content from disk, derive language
3//! ID from the extension, register the resulting
4//! `DocumentStateDTO` in `ApplicationState.Feature.Documents`,
5//! and return `{ uri, content, version, languageId }` to Wind.
6//!
7//! Version starts at 1 for fresh opens; an existing entry
8//! increments instead of resetting so concurrent re-opens
9//! don't desync VS Code's TextDocument observers.
10
11use std::sync::Arc;
12
13use serde_json::{Value, json};
14
15use crate::{
16	ApplicationState::DTO::DocumentStateDTO::DocumentStateDTO,
17	RunTime::ApplicationRunTime::ApplicationRunTime,
18};
19
20pub async fn Fn(RunTime:Arc<ApplicationRunTime>, Arguments:Vec<Value>) -> Result<Value, String> {
21	let Uri = Arguments
22		.first()
23		.and_then(|V| V.as_str())
24		.ok_or("model:open requires uri".to_string())?
25		.to_owned();
26
27	let FilePath = if let Some(stripped) = Uri.strip_prefix("file://") {
28		stripped.to_owned()
29	} else {
30		Uri.clone()
31	};
32
33	let Content = tokio::fs::read_to_string(&FilePath).await.unwrap_or_default();
34
35	let LanguageId = std::path::Path::new(&FilePath)
36		.extension()
37		.and_then(|E| E.to_str())
38		.map(|Ext| {
39			match Ext {
40				"rs" => "rust",
41				"ts" | "tsx" => "typescript",
42				"js" | "jsx" | "mjs" | "cjs" => "javascript",
43				"json" | "jsonc" => "json",
44				"toml" => "toml",
45				"yaml" | "yml" => "yaml",
46				"md" => "markdown",
47				"html" | "htm" => "html",
48				"css" | "scss" | "less" => "css",
49				"sh" | "bash" | "zsh" => "shellscript",
50				"py" => "python",
51				"go" => "go",
52				"c" | "h" => "c",
53				"cpp" | "cc" | "cxx" | "hpp" => "cpp",
54				_ => "plaintext",
55			}
56		})
57		.unwrap_or("plaintext")
58		.to_owned();
59
60	let Version = RunTime
61		.Environment
62		.ApplicationState
63		.Feature
64		.Documents
65		.Get(&Uri)
66		.map(|D| D.Version + 1)
67		.unwrap_or(1);
68
69	if let Ok(ParsedUri) = url::Url::parse(&Uri) {
70		let Lines:Vec<String> = Content.lines().map(|L| L.to_owned()).collect();
71
72		let Eol = if Content.contains("\r\n") { "\r\n" } else { "\n" }.to_owned();
73
74		let Document = DocumentStateDTO {
75			URI:ParsedUri,
76
77			LanguageIdentifier:LanguageId.clone(),
78
79			Version,
80
81			Lines,
82
83			EOL:Eol,
84
85			IsDirty:false,
86
87			Encoding:"utf-8".to_owned(),
88
89			VersionIdentifier:Version,
90		};
91
92		RunTime
93			.Environment
94			.ApplicationState
95			.Feature
96			.Documents
97			.AddOrUpdate(Uri.clone(), Document);
98	}
99
100	// Track the active document so `nativeHost:getWindows` and the Cocoon
101	// `vscode.window.activeTextEditor` shim stay in sync.
102	RunTime
103		.Environment
104		.ApplicationState
105		.Workspace
106		.SetActiveDocumentURI(Some(Uri.clone()));
107
108	// Fire-and-forget: notify Cocoon so its `window.activeTextEditor` shim
109	// updates. The notification is cheap (~gRPC framing + 1 JSON field); if
110	// Cocoon isn't connected yet the `SendNotification` error is swallowed.
111	let NotifyUri = Uri.clone();
112
113	let NotifyLang = LanguageId.clone();
114
115	let NotifyVer = Version;
116
117	tokio::spawn(async move {
118		let _ = crate::Vine::Client::SendNotification::Fn(
119			"cocoon-main".to_string(),
120			"window.didChangeActiveTextEditor".to_string(),
121			serde_json::json!({ "uri": NotifyUri, "languageId": NotifyLang, "version": NotifyVer }),
122		)
123		.await;
124	});
125
126	Ok(json!({
127		"uri": Uri,
128		"content": Content,
129		"version": Version,
130		"languageId": LanguageId,
131	}))
132}