Skip to main content

DevelopmentNodeEnvironment_MicrosoftVSCodeDependency_22NodeVersion_Bundle_Clean_Debug_ElectronProfile_EsbuildCompiler_Mountain/RPC/CocoonService/GenericRequest/
Dispatcher.rs

1//! Dispatcher for the generic `process_mountain_request` gRPC endpoint.
2//!
3//! Legacy JSON-over-gRPC rail used by Cocoon's
4//! `MountainGRPCClient.sendRequest(method, params)` for method names that
5//! predate the typed proto endpoints. Match arms call into Mountain's
6//! environment directly via `Service.environment.*`.
7
8use std::time::UNIX_EPOCH;
9
10use serde_json::json;
11use tonic::{Request, Response, Status};
12use url::Url;
13use CommonLibrary::{
14	Command::CommandExecutor::CommandExecutor,
15	LanguageFeature::{
16		DTO::PositionDTO::PositionDTO,
17		LanguageFeatureProviderRegistry::LanguageFeatureProviderRegistry,
18	},
19};
20
21use crate::{
22	RPC::CocoonService::CocoonServiceImpl,
23	Vine::Generated::{GenericRequest as GenericRequestMsg, GenericResponse, RpcError},
24	dev_log,
25};
26
27pub async fn Fn(
28	Service:&CocoonServiceImpl,
29
30	request:Request<GenericRequestMsg>,
31) -> Result<Response<GenericResponse>, Status> {
32	let Req = request.into_inner();
33
34	let RequestId = Req.request_identifier;
35
36	dev_log!(
37		"cocoon",
38		"[CocoonService] generic request: method={} id={}",
39		Req.method,
40		RequestId
41	);
42
43	/// Serialise a value into the `result` bytes of a GenericResponse.
44	fn OkResponse(RequestId:u64, Value:&impl serde::Serialize) -> Response<GenericResponse> {
45		let Bytes = serde_json::to_vec(Value).unwrap_or_default();
46
47		Response::new(GenericResponse { request_identifier:RequestId, result:Bytes, error:None })
48	}
49
50	/// Build an error GenericResponse.
51	fn ErrResponse(RequestId:u64, Code:i32, Message:String) -> Response<GenericResponse> {
52		Response::new(GenericResponse {
53			request_identifier:RequestId,
54			result:Vec::new(),
55			error:Some(RpcError { code:Code, message:Message, data:Vec::new() }),
56		})
57	}
58
59	// Deserialise the generic parameter bytes as a JSON value
60	let Params:serde_json::Value = if Req.parameter.is_empty() {
61		serde_json::Value::Null
62	} else {
63		serde_json::from_slice(&Req.parameter).unwrap_or(serde_json::Value::Null)
64	};
65
66	match Req.method.as_str() {
67		// ---- File System ---- (Cocoon FileSystemService uses these paths)
68		"fs.readFile" | "file:read" => {
69			let Path = Params
70				.as_str()
71				.or_else(|| Params.get("path").and_then(|V| V.as_str()))
72				.unwrap_or("");
73
74			match tokio::fs::read(Path).await {
75				Ok(Content) => Ok(OkResponse(RequestId, &Content)),
76
77				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.readFile: {}", Error))),
78			}
79		},
80
81		"fs.writeFile" | "file:write" => {
82			let Path = Params.get("path").and_then(|V| V.as_str()).unwrap_or("");
83
84			let Content:Vec<u8> = Params
85				.get("content")
86				.and_then(|V| V.as_array())
87				.map(|A| A.iter().filter_map(|B| B.as_u64().map(|N| N as u8)).collect())
88				.unwrap_or_default();
89
90			match tokio::fs::write(Path, &Content).await {
91				Ok(()) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
92
93				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.writeFile: {}", Error))),
94			}
95		},
96
97		"fs.stat" | "file:stat" => {
98			let Path = Params
99				.as_str()
100				.or_else(|| Params.get("path").and_then(|V| V.as_str()))
101				.unwrap_or("");
102
103			match tokio::fs::metadata(Path).await {
104				Ok(Meta) => {
105					let Mtime = Meta
106						.modified()
107						.ok()
108						.and_then(|T| T.duration_since(UNIX_EPOCH).ok())
109						.map(|D| D.as_millis() as u64)
110						.unwrap_or(0);
111
112					Ok(OkResponse(
113						RequestId,
114						&json!({
115							"type": if Meta.is_dir() { 2 } else { 1 },
116							"is_file": Meta.is_file(),
117							"is_directory": Meta.is_dir(),
118							"size": Meta.len(),
119							"mtime": Mtime,
120						}),
121					))
122				},
123
124				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.stat: {}", Error))),
125			}
126		},
127
128		"fs.listDir" | "fs.readdir" | "file:readdir" => {
129			let Path = Params
130				.as_str()
131				.or_else(|| Params.get("path").and_then(|V| V.as_str()))
132				.unwrap_or("");
133
134			match tokio::fs::read_dir(Path).await {
135				Ok(mut Entries) => {
136					// Return [{name, type}] where type 1=File 2=Directory
137					let mut Items:Vec<serde_json::Value> = Vec::new();
138
139					while let Ok(Some(Entry)) = Entries.next_entry().await {
140						if let Some(Name) = Entry.file_name().to_str() {
141							let IsDir = Entry.file_type().await.map(|T| T.is_dir()).unwrap_or(false);
142
143							Items.push(json!({ "name": Name, "type": if IsDir { 2u32 } else { 1u32 } }));
144						}
145					}
146
147					Ok(OkResponse(RequestId, &Items))
148				},
149
150				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.listDir: {}", Error))),
151			}
152		},
153
154		"fs.createDir" | "file:mkdir" => {
155			let Path = Params
156				.as_str()
157				.or_else(|| Params.get("path").and_then(|V| V.as_str()))
158				.unwrap_or("");
159
160			match tokio::fs::create_dir_all(Path).await {
161				Ok(()) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
162
163				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.createDir: {}", Error))),
164			}
165		},
166
167		"fs.delete" | "file:delete" => {
168			let Path = Params
169				.as_str()
170				.or_else(|| Params.get("path").and_then(|V| V.as_str()))
171				.unwrap_or("");
172
173			let Result = if std::path::Path::new(Path).is_dir() {
174				tokio::fs::remove_dir_all(Path).await
175			} else {
176				tokio::fs::remove_file(Path).await
177			};
178
179			match Result {
180				Ok(()) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
181
182				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.delete: {}", Error))),
183			}
184		},
185
186		"fs.rename" | "file:move" => {
187			let From = Params.get("from").and_then(|V| V.as_str()).unwrap_or("");
188
189			let To = Params.get("to").and_then(|V| V.as_str()).unwrap_or("");
190
191			match tokio::fs::rename(From, To).await {
192				Ok(()) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
193
194				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("fs.rename: {}", Error))),
195			}
196		},
197
198		// ---- Commands ----
199		"commands.execute" => {
200			let CommandId = Params.get("id").and_then(|V| V.as_str()).unwrap_or("").to_string();
201
202			let Arg = Params.get("arg").cloned().unwrap_or(serde_json::Value::Null);
203
204			match Service.environment.ExecuteCommand(CommandId, Arg).await {
205				Ok(Value) => Ok(OkResponse(RequestId, &Value)),
206
207				Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
208			}
209		},
210
211		// ---- Commands (Cocoon MountainGRPCClient format) ----
212		"executeCommand" => {
213			let CommandId = Params.get("commandId").and_then(|V| V.as_str()).unwrap_or("").to_string();
214
215			let Arg = Params
216				.get("arguments")
217				.and_then(|A| A.as_array())
218				.and_then(|A| A.first())
219				.cloned()
220				.unwrap_or(serde_json::Value::Null);
221
222			match Service.environment.ExecuteCommand(CommandId, Arg).await {
223				Ok(Value) => Ok(OkResponse(RequestId, &json!({ "result": Value }))),
224
225				Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
226			}
227		},
228
229		"unregisterCommand" => {
230			let ExtensionId = Params.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
231
232			let CommandId = Params.get("commandId").and_then(|V| V.as_str()).unwrap_or("").to_string();
233
234			match Service.environment.UnregisterCommand(ExtensionId, CommandId).await {
235				Ok(()) => Ok(OkResponse(RequestId, &json!({ "success": true }))),
236
237				Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
238			}
239		},
240
241		// ---- Window dialogs (Window.ts method names) ----
242		"UserInterface.ShowOpenDialog" => {
243			use CommonLibrary::UserInterface::{
244				DTO::OpenDialogOptionsDTO::OpenDialogOptionsDTO,
245				UserInterfaceProvider::UserInterfaceProvider,
246			};
247
248			let Title = Params
249				.get(0)
250				.and_then(|V| V.get("title"))
251				.and_then(|T| T.as_str())
252				.map(|S| S.to_string());
253
254			let Options = OpenDialogOptionsDTO {
255				Base:CommonLibrary::UserInterface::DTO::DialogOptionsDTO::DialogOptionsDTO {
256					Title,
257					..Default::default()
258				},
259				..OpenDialogOptionsDTO::default()
260			};
261
262			match Service.environment.ShowOpenDialog(Some(Options)).await {
263				Ok(Some(Paths)) => {
264					let Uris:Vec<String> = Paths.iter().map(|P| format!("file://{}", P.display())).collect();
265
266					Ok(OkResponse(RequestId, &json!(Uris)))
267				},
268
269				Ok(None) => Ok(OkResponse(RequestId, &json!(serde_json::Value::Array(vec![])))),
270
271				Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
272			}
273		},
274
275		"UserInterface.ShowSaveDialog" => {
276			use CommonLibrary::UserInterface::{
277				DTO::SaveDialogOptionsDTO::SaveDialogOptionsDTO,
278				UserInterfaceProvider::UserInterfaceProvider,
279			};
280
281			let Title = Params
282				.get(0)
283				.and_then(|V| V.get("title"))
284				.and_then(|T| T.as_str())
285				.map(|S| S.to_string());
286
287			let Options = SaveDialogOptionsDTO {
288				Base:CommonLibrary::UserInterface::DTO::DialogOptionsDTO::DialogOptionsDTO {
289					Title,
290					..Default::default()
291				},
292				..SaveDialogOptionsDTO::default()
293			};
294
295			match Service.environment.ShowSaveDialog(Some(Options)).await {
296				Ok(Some(Path)) => Ok(OkResponse(RequestId, &json!(format!("file://{}", Path.display())))),
297
298				Ok(None) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
299
300				Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
301			}
302		},
303
304		"UserInterface.ShowInputBox" => {
305			use CommonLibrary::UserInterface::{
306				DTO::InputBoxOptionsDTO::InputBoxOptionsDTO,
307				UserInterfaceProvider::UserInterfaceProvider,
308			};
309
310			let Opts = Params.get(0);
311
312			let Options = InputBoxOptionsDTO {
313				Prompt:Opts
314					.and_then(|V| V.get("prompt"))
315					.and_then(|P| P.as_str())
316					.map(|S| S.to_string()),
317
318				PlaceHolder:Opts
319					.and_then(|V| V.get("placeHolder"))
320					.and_then(|P| P.as_str())
321					.map(|S| S.to_string()),
322
323				IsPassword:Some(Opts.and_then(|V| V.get("password")).and_then(|B| B.as_bool()).unwrap_or(false)),
324
325				Value:Opts
326					.and_then(|V| V.get("value"))
327					.and_then(|V| V.as_str())
328					.map(|S| S.to_string()),
329
330				Title:None,
331
332				IgnoreFocusOut:None,
333			};
334
335			match Service.environment.ShowInputBox(Some(Options)).await {
336				Ok(Some(Text)) => Ok(OkResponse(RequestId, &json!(Text))),
337
338				Ok(None) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
339
340				Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
341			}
342		},
343
344		// ---- Native shell operations ----
345		"openExternal" => {
346			use tauri::Emitter;
347
348			let Url = Params
349				.as_str()
350				.or_else(|| Params.get("url").and_then(|V| V.as_str()))
351				.unwrap_or("")
352				.to_string();
353
354			// Emit to Sky - Sky uses Tauri shell plugin to open the URL
355			let _ = Service
356				.environment
357				.ApplicationHandle
358				.emit("sky://native/openExternal", json!({ "url": Url }));
359
360			Ok(OkResponse(RequestId, &json!({ "success": true })))
361		},
362
363		// ---- Window (Cocoon MountainGRPCClient format) ----
364		"showTextDocument" => {
365			use tauri::Emitter;
366
367			let Uri = Params
368				.get("uri")
369				.and_then(|V| V.get("value").or(Some(V)))
370				.and_then(|V| V.as_str())
371				.unwrap_or("")
372				.to_string();
373
374			let ViewColumn = Params.get("viewColumn").and_then(|V| V.as_i64()).map(|N| N + 2);
375
376			let PreserveFocus = Params.get("preserveFocus").and_then(|V| V.as_bool()).unwrap_or(false);
377
378			let _ = Service.environment.ApplicationHandle.emit(
379				"sky://editor/openDocument",
380				json!({ "uri": Uri, "viewColumn": ViewColumn, "preserveFocus": PreserveFocus }),
381			);
382
383			Ok(OkResponse(RequestId, &json!({ "success": true })))
384		},
385
386		"showInformation" => {
387			use CommonLibrary::UserInterface::{
388				DTO::MessageSeverity::MessageSeverity,
389				UserInterfaceProvider::UserInterfaceProvider,
390			};
391
392			let Message = Params.get("message").and_then(|V| V.as_str()).unwrap_or("").to_string();
393
394			let Items:Option<serde_json::Value> = Params
395				.get("items")
396				.cloned()
397				.filter(|V| V.is_array() && !V.as_array().unwrap().is_empty());
398
399			match Service.environment.ShowMessage(MessageSeverity::Info, Message, Items).await {
400				Ok(Some(Selected)) => Ok(OkResponse(RequestId, &json!({ "selectedItem": Selected }))),
401
402				Ok(None) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
403
404				Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
405			}
406		},
407
408		"showWarning" => {
409			use CommonLibrary::UserInterface::{
410				DTO::MessageSeverity::MessageSeverity,
411				UserInterfaceProvider::UserInterfaceProvider,
412			};
413
414			let Message = Params.get("message").and_then(|V| V.as_str()).unwrap_or("").to_string();
415
416			let Items:Option<serde_json::Value> = Params
417				.get("items")
418				.cloned()
419				.filter(|V| V.is_array() && !V.as_array().unwrap().is_empty());
420
421			match Service.environment.ShowMessage(MessageSeverity::Warning, Message, Items).await {
422				Ok(Some(Selected)) => Ok(OkResponse(RequestId, &json!({ "selectedItem": Selected }))),
423
424				Ok(None) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
425
426				Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
427			}
428		},
429
430		"showError" => {
431			use CommonLibrary::UserInterface::{
432				DTO::MessageSeverity::MessageSeverity,
433				UserInterfaceProvider::UserInterfaceProvider,
434			};
435
436			let Message = Params.get("message").and_then(|V| V.as_str()).unwrap_or("").to_string();
437
438			let Items:Option<serde_json::Value> = Params
439				.get("items")
440				.cloned()
441				.filter(|V| V.is_array() && !V.as_array().unwrap().is_empty());
442
443			match Service.environment.ShowMessage(MessageSeverity::Error, Message, Items).await {
444				Ok(Some(Selected)) => Ok(OkResponse(RequestId, &json!({ "selectedItem": Selected }))),
445
446				Ok(None) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
447
448				Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
449			}
450		},
451
452		"createStatusBarItem" => {
453			use tauri::Emitter;
454
455			let Id = Params.get("id").and_then(|V| V.as_str()).unwrap_or("").to_string();
456
457			let Text = Params.get("text").and_then(|V| V.as_str()).unwrap_or("").to_string();
458
459			let Tooltip = Params.get("tooltip").and_then(|V| V.as_str()).unwrap_or("").to_string();
460
461			// Sky's `SetOrUpdateEntry` (`SkyBridge.ts:744`) listens on
462			// `sky://statusbar/set-entry` and `sky://statusbar/update`
463			// - both route through the same upsert. There is no
464			// `sky://statusbar/create` listener; emit the canonical
465			// `set-entry` channel so the entry materialises on first
466			// register.
467			let _ = Service.environment.ApplicationHandle.emit(
468				"sky://statusbar/set-entry",
469				json!({ "id": Id, "text": Text, "tooltip": Tooltip }),
470			);
471
472			Ok(OkResponse(RequestId, &json!({ "itemId": Id })))
473		},
474
475		"setStatusBarText" => {
476			use tauri::Emitter;
477
478			let ItemId = Params.get("itemId").and_then(|V| V.as_str()).unwrap_or("").to_string();
479
480			let Text = Params.get("text").and_then(|V| V.as_str()).unwrap_or("").to_string();
481
482			let _ = Service
483				.environment
484				.ApplicationHandle
485				.emit("sky://statusbar/update", json!({ "id": ItemId, "text": Text }));
486
487			Ok(OkResponse(RequestId, &json!({ "success": true })))
488		},
489
490		"createWebviewPanel" => {
491			use tauri::Emitter;
492
493			let ViewType = Params.get("viewType").and_then(|V| V.as_str()).unwrap_or("").to_string();
494
495			let Title = Params.get("title").and_then(|V| V.as_str()).unwrap_or("").to_string();
496
497			let Handle = std::time::SystemTime::now()
498				.duration_since(std::time::UNIX_EPOCH)
499				.map(|D| D.as_millis() as u64)
500				.unwrap_or(0);
501
502			let _ = Service.environment.ApplicationHandle.emit("sky://webview/create", json!({ "handle": Handle, "viewType": ViewType, "title": Title, "viewColumn": Params.get("viewColumn"), "preserveFocus": Params.get("preserveFocus").and_then(|V| V.as_bool()).unwrap_or(false) }));
503
504			Ok(OkResponse(RequestId, &json!({ "handle": Handle })))
505		},
506
507		"setWebviewHtml" => {
508			use tauri::Emitter;
509
510			let Handle = Params.get("handle").and_then(|V| V.as_u64()).unwrap_or(0);
511
512			let Html = Params.get("html").and_then(|V| V.as_str()).unwrap_or("").to_string();
513
514			// Canonical kebab-case channel; `sky://webview/setHtml` retired.
515			let _ = Service
516				.environment
517				.ApplicationHandle
518				.emit("sky://webview/set-html", json!({ "handle": Handle, "html": Html }));
519
520			Ok(OkResponse(RequestId, &json!({ "success": true })))
521		},
522
523		// ---- Workspace (Cocoon MountainGRPCClient format) ----
524		// `findFiles` / `findTextInFiles` are called by Cocoon's
525		// `workspace.findFiles()` / `workspace.findTextInFiles()`
526		// API shims. Delegate to the real trait implementations
527		// (`WorkspaceProvider::FindFilesInWorkspace`,
528		// `SearchProvider::TextSearch`) which use `ignore::WalkBuilder`
529		// + `grep-searcher` - respecting `.gitignore`, doing parallel
530		// walks, and producing properly-constructed `Url` results.
531		// Prior inline implementations used naive dir-walks, hidden-
532		// dot skipping, and `format!("file://{}", path)` URI
533		// construction that mangled non-ASCII paths.
534		"findFiles" => {
535			use CommonLibrary::Workspace::WorkspaceProvider::WorkspaceProvider;
536
537			let Include = Params
538				.get("pattern")
539				.cloned()
540				.or_else(|| Params.get("include").cloned())
541				.unwrap_or(serde_json::Value::String("**".into()));
542
543			let Exclude = Params.get("exclude").cloned().filter(|V| !V.is_null());
544
545			let MaxResults = Params.get("maxResults").and_then(|V| V.as_u64()).map(|N| N as usize);
546
547			let UseIgnoreFiles = Params.get("useIgnoreFiles").and_then(|V| V.as_bool()).unwrap_or(true);
548
549			let FollowSymlinks = Params.get("followSymlinks").and_then(|V| V.as_bool()).unwrap_or(false);
550
551			match Service
552				.environment
553				.FindFilesInWorkspace(Include, Exclude, MaxResults, UseIgnoreFiles, FollowSymlinks)
554				.await
555			{
556				Ok(Urls) => {
557					Ok(OkResponse(
558						RequestId,
559						&json!({ "uris": Urls.into_iter().map(|U| U.to_string()).collect::<Vec<_>>() }),
560					))
561				},
562
563				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("findFiles: {}", Error))),
564			}
565		},
566
567		"findTextInFiles" => {
568			use CommonLibrary::Search::SearchProvider::SearchProvider;
569
570			// VS Code's `workspace.findTextInFiles` takes a
571			// `TextSearchQuery` in field `pattern` (or passed flat
572			// at the top level). Accept both shapes.
573			let QueryValue = if Params.get("pattern").map(|V| V.is_object()).unwrap_or(false) {
574				Params.get("pattern").cloned().unwrap_or(serde_json::Value::Null)
575			} else if Params.get("pattern").map(|V| V.is_string()).unwrap_or(false) {
576				json!({
577					"pattern": Params.get("pattern").and_then(|V| V.as_str()).unwrap_or(""),
578					"isRegExp": Params.get("isRegExp").and_then(|V| V.as_bool()).unwrap_or(false),
579					"isCaseSensitive": Params.get("isCaseSensitive").and_then(|V| V.as_bool()).unwrap_or(false),
580					"isWordMatch": Params.get("isWordMatch").and_then(|V| V.as_bool()).unwrap_or(false),
581				})
582			} else {
583				Params.clone()
584			};
585
586			let OptionsValue = Params.get("options").cloned().unwrap_or(serde_json::Value::Null);
587
588			match Service.environment.TextSearch(QueryValue, OptionsValue).await {
589				Ok(Matches) => Ok(OkResponse(RequestId, &json!({ "matches": Matches }))),
590
591				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("findTextInFiles: {}", Error))),
592			}
593		},
594
595		"openDocument" => {
596			use tauri::Emitter;
597
598			let Uri = Params
599				.get("uri")
600				.and_then(|V| V.get("value").or(Some(V)))
601				.and_then(|V| V.as_str())
602				.unwrap_or("")
603				.to_string();
604
605			let ViewColumn = Params.get("viewColumn").and_then(|V| V.as_i64());
606
607			let _ = Service
608				.environment
609				.ApplicationHandle
610				.emit("sky://editor/openDocument", json!({ "uri": Uri, "viewColumn": ViewColumn }));
611
612			Ok(OkResponse(RequestId, &json!({ "success": true })))
613		},
614
615		"saveAll" => {
616			use tauri::Emitter;
617
618			let IncludeUntitled = Params.get("includeUntitled").and_then(|V| V.as_bool()).unwrap_or(false);
619
620			let _ = Service
621				.environment
622				.ApplicationHandle
623				.emit("sky://editor/saveAll", json!({ "includeUntitled": IncludeUntitled }));
624
625			Ok(OkResponse(RequestId, &json!({ "success": true })))
626		},
627
628		"applyEdit" => {
629			use tauri::Emitter;
630
631			let Uri = Params
632				.get("uri")
633				.and_then(|V| V.get("value").or(Some(V)))
634				.and_then(|V| V.as_str())
635				.unwrap_or("")
636				.to_string();
637
638			let Edits = Params.get("edits").cloned().unwrap_or(json!([]));
639
640			let _ = Service
641				.environment
642				.ApplicationHandle
643				.emit("sky://editor/applyEdits", json!({ "uri": Uri, "edits": Edits }));
644
645			Ok(OkResponse(RequestId, &json!({ "success": true })))
646		},
647
648		// ---- Secret Storage (Cocoon MountainGRPCClient format) ----
649		"getSecret" => {
650			use CommonLibrary::Secret::SecretProvider::SecretProvider;
651
652			let ExtensionId = Params.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
653
654			let Key = Params.get("key").and_then(|V| V.as_str()).unwrap_or("").to_string();
655
656			match Service.environment.GetSecret(ExtensionId, Key).await {
657				Ok(Some(Value)) => Ok(OkResponse(RequestId, &json!({ "value": Value }))),
658
659				Ok(None) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
660
661				Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
662			}
663		},
664
665		"storeSecret" => {
666			use CommonLibrary::Secret::SecretProvider::SecretProvider;
667
668			let ExtensionId = Params.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
669
670			let Key = Params.get("key").and_then(|V| V.as_str()).unwrap_or("").to_string();
671
672			let Value = Params.get("value").and_then(|V| V.as_str()).unwrap_or("").to_string();
673
674			match Service.environment.StoreSecret(ExtensionId, Key, Value).await {
675				Ok(()) => Ok(OkResponse(RequestId, &json!({ "success": true }))),
676
677				Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
678			}
679		},
680
681		"deleteSecret" => {
682			use CommonLibrary::Secret::SecretProvider::SecretProvider;
683
684			let ExtensionId = Params.get("extensionId").and_then(|V| V.as_str()).unwrap_or("").to_string();
685
686			let Key = Params.get("key").and_then(|V| V.as_str()).unwrap_or("").to_string();
687
688			match Service.environment.DeleteSecret(ExtensionId, Key).await {
689				Ok(()) => Ok(OkResponse(RequestId, &json!({ "success": true }))),
690
691				Err(Error) => Ok(ErrResponse(RequestId, -32000, Error.to_string())),
692			}
693		},
694
695		// ---- FS aliases (Cocoon MountainGRPCClient uses different key names) ----
696		"readFile" => {
697			let Uri = Params
698				.get("uri")
699				.and_then(|V| V.as_str())
700				.or_else(|| Params.as_str())
701				.unwrap_or("")
702				.replace("file://", "");
703
704			match tokio::fs::read(&Uri).await {
705				Ok(Content) => Ok(OkResponse(RequestId, &Content)),
706
707				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("readFile: {}", Error))),
708			}
709		},
710
711		"writeFile" => {
712			let Uri = Params.get("uri").and_then(|V| V.as_str()).unwrap_or("").replace("file://", "");
713
714			let Content:Vec<u8> = Params
715				.get("content")
716				.and_then(|V| V.as_array())
717				.map(|A| A.iter().filter_map(|B| B.as_u64().map(|N| N as u8)).collect())
718				.unwrap_or_default();
719
720			match tokio::fs::write(&Uri, &Content).await {
721				Ok(()) => Ok(OkResponse(RequestId, &serde_json::Value::Null)),
722
723				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("writeFile: {}", Error))),
724			}
725		},
726
727		"stat" => {
728			let Uri = Params
729				.get("uri")
730				.and_then(|V| V.as_str())
731				.or_else(|| Params.as_str())
732				.unwrap_or("")
733				.replace("file://", "");
734
735			match tokio::fs::metadata(&Uri).await {
736				Ok(Meta) => {
737					let Mtime = Meta
738						.modified()
739						.ok()
740						.and_then(|T| T.duration_since(UNIX_EPOCH).ok())
741						.map(|D| D.as_millis() as u64)
742						.unwrap_or(0);
743
744					Ok(OkResponse(
745						RequestId,
746						&json!({ "type": if Meta.is_dir() { 2 } else { 1 }, "is_file": Meta.is_file(), "is_directory": Meta.is_dir(), "size": Meta.len(), "mtime": Mtime }),
747					))
748				},
749
750				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("stat: {}", Error))),
751			}
752		},
753
754		"readdir" => {
755			let Uri = Params
756				.get("uri")
757				.and_then(|V| V.as_str())
758				.or_else(|| Params.as_str())
759				.unwrap_or("")
760				.replace("file://", "");
761
762			match tokio::fs::read_dir(&Uri).await {
763				Ok(mut Entries) => {
764					let mut Names:Vec<String> = Vec::new();
765
766					while let Ok(Some(Entry)) = Entries.next_entry().await {
767						if let Some(Name) = Entry.file_name().to_str() {
768							Names.push(Name.to_string());
769						}
770					}
771
772					Ok(OkResponse(RequestId, &Names))
773				},
774
775				Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("readdir: {}", Error))),
776			}
777		},
778
779		// ---- Call Hierarchy / Type Hierarchy (T1.5 Approach A) ----
780		// These method names come from Cocoon's language provider when the
781		// user triggers F12 / Go to References / Call Hierarchy. They use
782		// the generic JSON request channel instead of typed proto methods
783		// because `PrepareCallHierarchy` was never added to Vine.proto.
784		// Params shape: `{ uri, position: { line, character } }`.
785		"$provideCallHierarchyItems" | "prepareCallHierarchy" => {
786			let URI_Raw = Params.get("uri").and_then(|V| V.as_str()).unwrap_or("");
787
788			let Line = Params
789				.get("position")
790				.and_then(|P| P.get("line"))
791				.and_then(|V| V.as_u64())
792				.unwrap_or(0);
793
794			let Char = Params
795				.get("position")
796				.and_then(|P| P.get("character"))
797				.and_then(|V| V.as_u64())
798				.unwrap_or(0);
799
800			match Url::parse(URI_Raw) {
801				Ok(DocURI) => {
802					let Pos = PositionDTO { LineNumber:Line as u32, Column:Char as u32 };
803
804					match Service.environment.PrepareCallHierarchy(DocURI, Pos).await {
805						Ok(Result) => Ok(OkResponse(RequestId, &Result)),
806
807						Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("prepareCallHierarchy: {}", Error))),
808					}
809				},
810
811				Err(_) => Ok(OkResponse(RequestId, &serde_json::Value::Array(Vec::new()))),
812			}
813		},
814
815		"$provideTypeHierarchyItems" | "prepareTypeHierarchy" => {
816			let URI_Raw = Params.get("uri").and_then(|V| V.as_str()).unwrap_or("");
817
818			let Line = Params
819				.get("position")
820				.and_then(|P| P.get("line"))
821				.and_then(|V| V.as_u64())
822				.unwrap_or(0);
823
824			let Char = Params
825				.get("position")
826				.and_then(|P| P.get("character"))
827				.and_then(|V| V.as_u64())
828				.unwrap_or(0);
829
830			match Url::parse(URI_Raw) {
831				Ok(DocURI) => {
832					let Pos = PositionDTO { LineNumber:Line as u32, Column:Char as u32 };
833
834					match Service.environment.PrepareTypeHierarchy(DocURI, Pos).await {
835						Ok(Result) => Ok(OkResponse(RequestId, &Result)),
836
837						Err(Error) => Ok(ErrResponse(RequestId, -32000, format!("prepareTypeHierarchy: {}", Error))),
838					}
839				},
840
841				Err(_) => Ok(OkResponse(RequestId, &serde_json::Value::Array(Vec::new()))),
842			}
843		},
844
845		// ---- Unknown ----
846		_ => {
847			dev_log!("cocoon", "warn: [CocoonService] Unknown generic method: {}", Req.method);
848
849			Ok(ErrResponse(RequestId, -32601, format!("Method '{}' not found", Req.method)))
850		},
851	}
852}