Skip to main content

Mountain/RPC/CocoonService/FileSystem/
WatchFile.rs

1//! `watch_file` gRPC endpoint - Cocoon calls this when an extension uses
2//! `vscode.workspace.createFileSystemWatcher`. Routes to Mountain's
3//! `FileWatcherProvider::RegisterWatcher` so OS events (FSEvents on macOS,
4//! inotify on Linux) flow back to Cocoon as `$fileWatcher:event` gRPC
5//! notifications which Cocoon fans out to extension `onDidChangeFile`
6//! listeners.
7//!
8//! The proto `WatchFileRequest` only carries a `uri` field. We derive the
9//! watch handle from a hash of the URI so dedup-by-triple logic in
10//! `FileWatcherProvider` can collapse identical registrations from multiple
11//! extensions watching the same root.
12
13use std::{
14	path::PathBuf,
15	sync::atomic::{AtomicU64, Ordering},
16};
17
18use CommonLibrary::FileSystem::FileWatcherProvider::FileWatcherProvider;
19use tonic::{Response, Status};
20use ::Vine::Generated::{Empty, WatchFileRequest};
21
22use crate::{RPC::CocoonService::CocoonServiceImpl, dev_log};
23
24static WATCH_SEQ:AtomicU64 = AtomicU64::new(1);
25
26pub async fn Fn(Service:&CocoonServiceImpl, Request:WatchFileRequest) -> Result<Response<Empty>, Status> {
27	let URI = Request.uri.as_ref().map(|U| U.value.as_str()).unwrap_or("").to_string();
28
29	if URI.is_empty() {
30		return Ok(Response::new(Empty {}));
31	}
32
33	let Handle = format!("grpc-watch-{}", WATCH_SEQ.fetch_add(1, Ordering::Relaxed));
34
35	dev_log!("filewatcher", "[CocoonService] watch_file handle={} uri={}", Handle, URI);
36
37	let Root = if let Ok(Url) = url::Url::parse(&URI) {
38		Url.to_file_path().unwrap_or_else(|_| PathBuf::from(&URI))
39	} else {
40		PathBuf::from(&URI)
41	};
42
43	// Register recursive with no pattern filter - Cocoon's FileSystemWatcher
44	// subscribers apply their own glob matching on the extension side.
45	Service
46		.environment
47		.RegisterWatcher(Handle, Root, true, None)
48		.await
49		.map_err(|E| Status::internal(format!("watch_file: {E}")))?;
50
51	Ok(Response::new(Empty {}))
52}