Skip to main content

Mountain/IPC/WindServiceHandlers/FileSystem/Native/
FileWriteNative.rs

1#![allow(unused_variables, dead_code, unused_imports)]
2
3//! Wire method `file:write` / `file:writeFile`. Accepts either a plain
4//! string body or a `{ buffer: number[] | base64 }` VSBuffer. Parent
5//! directory is created best-effort. After a successful write, fires
6//! `$acceptModelSaved` to Cocoon so `onDidSaveTextDocument` reaches
7//! extensions (T1.4 save notification).
8
9use serde_json::{Value, json};
10
11use crate::{IPC::WindServiceHandlers::Utilities::PathExtraction::Fn as extract_path_from_arg, dev_log};
12
13pub async fn Fn(Arguments:Vec<Value>) -> Result<Value, String> {
14	let ResourceArg = Arguments.get(0).ok_or("Missing file path")?;
15
16	// Capture the `external` field (full file:// URI) from the URI object
17	// for the $acceptModelSaved notification before we consume the path.
18	let ExternalUri = ResourceArg
19		.as_object()
20		.and_then(|O| O.get("external"))
21		.and_then(|V| V.as_str())
22		.map(|S| S.to_string());
23
24	let Path = extract_path_from_arg(ResourceArg)?;
25
26	let Content = Arguments.get(1).ok_or("Missing file content")?;
27
28	let Bytes = if let Some(S) = Content.as_str() {
29		S.as_bytes().to_vec()
30	} else if let Some(Obj) = Content.as_object() {
31		if let Some(Buf) = Obj.get("buffer") {
32			if let Some(Arr) = Buf.as_array() {
33				Arr.iter().filter_map(|V| V.as_u64().map(|N| N as u8)).collect()
34			} else if let Some(S) = Buf.as_str() {
35				S.as_bytes().to_vec()
36			} else {
37				return Err("Unsupported buffer format".to_string());
38			}
39		} else {
40			serde_json::to_string(Content).unwrap_or_default().into_bytes()
41		}
42	} else {
43		return Err("File content must be a string or VSBuffer".to_string());
44	};
45
46	if let Some(Parent) = std::path::Path::new(&Path).parent() {
47		tokio::fs::create_dir_all(Parent).await.ok();
48	}
49
50	let Start = std::time::Instant::now();
51
52	tokio::fs::write(&Path, &Bytes)
53		.await
54		.map_err(|E| format!("Failed to write file: {} (path: {})", E, Path))?;
55
56	let ElapsedMs = Start.elapsed().as_millis();
57
58	dev_log!("vfs", "file:write ok path={} bytes={} ms={}", Path, Bytes.len(), ElapsedMs);
59
60	// T1.4 - notify Cocoon that the model on disk now matches the editor
61	// buffer so `onDidSaveTextDocument` fires for subscribed extensions.
62	// Build a file:// URI from `external` (preferred) or the path string.
63	let FileUri = ExternalUri.unwrap_or_else(|| format!("file://{}", Path));
64
65	tokio::spawn(async move {
66		if let Err(Error) = crate::Vine::Client::SendNotification::Fn(
67			"cocoon-main".to_string(),
68			"$acceptModelSaved".to_string(),
69			json!({ "uri": FileUri }),
70		)
71		.await
72		{
73			dev_log!("vfs", "warn: [FileWriteNative] $acceptModelSaved notify failed: {:?}", Error);
74		}
75	});
76
77	// Return mtime/size so VS Code's DiskFileSystemProvider can update its
78	// FileStatWithMetadata cache - prevents a spurious "file changed on disk"
79	// conflict caused by the pre-write etag being stale after the write.
80	match tokio::fs::metadata(&Path).await {
81		Ok(Meta) => Ok(crate::IPC::WindServiceHandlers::Utilities::MetadataEncoding::Fn(&Meta)),
82
83		Err(_) => Ok(Value::Null),
84	}
85}