Skip to main content

Mountain/Track/Effect/CreateEffectForRequest/
Workspace.rs

1#![allow(non_snake_case, unused_variables, dead_code, unused_imports)]
2
3//! # Workspace Effect (CreateEffectForRequest)
4//!
5//! Effect constructors for workspace-level RPC methods. Handles:
6//! - `applyEdit` and `showTextDocument` via round-trip to Sky through
7//!   `UserInterfaceProvider::SendUserInterfaceRequest` (resolves when Sky has
8//!   actually applied the edit or shown the document).
9//! - `Workspace.RequestResourceTrust` and `Workspace.IsResourceTrusted` return
10//!   a permissive `true` heuristic so `vscode.git` proceeds; single- window dev
11//!   runtime stays trust-by-default.
12//! - `$updateWorkspaceFolders` applies workspace folder additions/removals to
13//!   `ApplicationState.Workspace` and broadcasts the delta.
14
15use std::{future::Future, pin::Pin, sync::Arc};
16
17use serde_json::{Value, json};
18use tauri::Runtime;
19
20use crate::{RunTime::ApplicationRunTime::ApplicationRunTime, Track::Effect::MappedEffectType::MappedEffect, dev_log};
21
22pub fn CreateEffect<R:Runtime>(MethodName:&str, Parameters:Value) -> Option<Result<MappedEffect, String>> {
23	match MethodName {
24		"applyEdit" => {
25			let effect =
26				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
27					Box::pin(async move {
28						// Atom T1: round-trip via Mountain's request/reply plumbing so the
29						// extension's `await workspace.applyEdit(…)` resolves when Sky has
30						// actually applied the edit (or refused). Previously a synthetic
31						// `true` returned before the edit ran, racing listeners that
32						// expected post-apply state.
33						let Payload = if Parameters.is_array() {
34							Parameters.get(0).cloned().unwrap_or_default()
35						} else {
36							Parameters
37						};
38						crate::Environment::UserInterfaceProvider::SendUserInterfaceRequest(
39							&run_time.Environment,
40							"sky://workspace/applyEdit",
41							Payload,
42						)
43						.await
44						.map_err(|Error| {
45							dev_log!("ipc", "error: [applyEdit] Sky did not answer ({:?})", Error);
46							Error.to_string()
47						})
48					})
49				};
50
51			Some(Ok(Box::new(effect)))
52		},
53
54		"showTextDocument" => {
55			let effect =
56				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
57					Box::pin(async move {
58						// Atom T1: same round-trip as applyEdit. The canonical vscode
59						// return shape is a `TextEditor` - today Sky resolves with a
60						// thin `{ uri, viewColumn }` stub. Extensions that chain
61						// editor ops may still see undefined properties; that's a
62						// Sky-side enrichment task (T2 follow-up).
63						match crate::Environment::UserInterfaceProvider::SendUserInterfaceRequest(
64							&run_time.Environment,
65							"sky://window/showTextDocument",
66							Parameters,
67						)
68						.await
69						{
70							Ok(Value) => Ok(Value),
71							Err(Error) => {
72								dev_log!(
73									"ipc",
74									"warn: [showTextDocument] Sky did not answer ({:?}); returning null",
75									Error
76								);
77								Ok(json!(null))
78							},
79						}
80					})
81				};
82
83			Some(Ok(Box::new(effect)))
84		},
85
86		// Workspace-trust family. vscode.git's `Model.openRepository` calls
87		// `await workspace.requestResourceTrust({uri, message})` and
88		// `await workspace.isResourceTrusted(uri)` before constructing the
89		// Repository. The Cocoon `WrapWorkspaceNamespace` Proxy fallback
90		// already returns a permissive `true` heuristic so vscode.git
91		// proceeds; routing the same method names through Mountain here
92		// gives the canonical handler a place to live (and makes
93		// `MountainMethods` see them via `GenerateRouteManifest.sh`'s grep,
94		// which switches the Cocoon shim from heuristic-default to
95		// gRPC-routed automatically on the next manifest regeneration). A
96		// future round can replace the unconditional `true` with a real
97		// per-OS trust query (Gatekeeper / SmartScreen / xattrs); single-
98		// window dev runtime stays trust-by-default.
99		"Workspace.RequestResourceTrust" | "Workspace.IsResourceTrusted" => {
100			let effect =
101				move |_run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
102
103					Box::pin(async move {
104						Ok(json!({ "trusted": true }))
105					})
106				};
107
108			Some(Ok(Box::new(effect)))
109		},
110
111		"$updateWorkspaceFolders" => {
112			let effect =
113				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
114					Box::pin(async move {
115						let Payload = if Parameters.is_array() {
116							Parameters.get(0).cloned().unwrap_or_default()
117						} else {
118							Parameters
119						};
120						let Additions:Vec<(String, String)> = Payload
121							.get("additions")
122							.and_then(Value::as_array)
123							.map(|Array| {
124								Array
125									.iter()
126									.filter_map(|Entry| {
127										let Uri = Entry
128											.get("uri")
129											.and_then(|U| U.get("value").and_then(Value::as_str).or_else(|| U.as_str()))
130											.map(str::to_string)?;
131										let Name = Entry.get("name").and_then(Value::as_str).unwrap_or("").to_string();
132										Some((Uri, Name))
133									})
134									.collect()
135							})
136							.unwrap_or_default();
137						let Removals:Vec<String> = Payload
138							.get("removals")
139							.and_then(Value::as_array)
140							.map(|Array| {
141								Array
142									.iter()
143									.filter_map(|Entry| {
144										Entry
145											.get("uri")
146											.and_then(|U| U.get("value").and_then(Value::as_str).or_else(|| U.as_str()))
147											.map(str::to_string)
148									})
149									.collect()
150							})
151							.unwrap_or_default();
152
153						let Workspace = &run_time.Environment.ApplicationState.Workspace;
154						let mut Folders = Workspace.GetWorkspaceFolders();
155						Folders.retain(|F| !Removals.contains(&F.URI.to_string()));
156						let Base = Folders.len();
157						for (Index, (UriStr, Name)) in Additions.iter().enumerate() {
158							if let Ok(Url) = url::Url::parse(UriStr) {
159								if let Ok(Dto) =
160									crate::ApplicationState::DTO::WorkspaceFolderStateDTO::WorkspaceFolderStateDTO::New(
161										Url,
162										Name.clone(),
163										Base + Index,
164									) {
165									Folders.push(Dto);
166								}
167							}
168						}
169						crate::ApplicationState::State::WorkspaceState::WorkspaceDelta::UpdateWorkspaceFoldersAndNotify(
170							Workspace, Folders,
171						);
172						Ok(json!(null))
173					})
174				};
175
176			Some(Ok(Box::new(effect)))
177		},
178
179		_ => None,
180	}
181}