Mountain/ApplicationState/State/WorkspaceState/
WorkspaceDelta.rs1use CommonLibrary::IPC::SkyEvent::SkyEvent;
20use serde_json::json;
21
22use crate::{
23 ApplicationState::DTO::WorkspaceFolderStateDTO::WorkspaceFolderStateDTO,
24 IPC::SkyEmit::LogSkyEmit,
25 Vine::Client,
26 dev_log,
27};
28
29fn FolderToWire(Folder:&WorkspaceFolderStateDTO) -> serde_json::Value {
34 json!({
35 "uri": Folder.URI.to_string(),
36 "name": Folder.GetDisplayName(),
37 "index": Folder.Index,
38 })
39}
40
41pub async fn DispatchDeltaWorkspaceFolders(Added:Vec<WorkspaceFolderStateDTO>, Removed:Vec<WorkspaceFolderStateDTO>) {
49 if Added.is_empty() && Removed.is_empty() {
50 return;
51 }
52
53 let AddedWire:Vec<serde_json::Value> = Added.iter().map(FolderToWire).collect();
54
55 let RemovedWire:Vec<serde_json::Value> = Removed.iter().map(FolderToWire).collect();
56
57 dev_log!(
58 "workspaces",
59 "[LandFix:WsDelta] $deltaWorkspaceFolders +{} -{} (first added={})",
60 AddedWire.len(),
61 RemovedWire.len(),
62 Added.first().map(|F| F.URI.as_str()).unwrap_or("<none>")
63 );
64
65 let Payload = json!({
66 "added": AddedWire,
67 "removed": RemovedWire,
68 });
69
70 if let Err(Error) =
71 Client::SendNotification::Fn("cocoon-main".to_string(), "$deltaWorkspaceFolders".to_string(), Payload).await
72 {
73 dev_log!(
74 "workspaces",
75 "warn: [LandFix:WsDelta] $deltaWorkspaceFolders notification failed: {}",
76 Error
77 );
78 }
79}
80
81pub fn UpdateWorkspaceFoldersAndNotify(
88 State:&crate::ApplicationState::State::WorkspaceState::WorkspaceState::State,
89
90 Folders:Vec<WorkspaceFolderStateDTO>,
91) {
92 let (Added, Removed) = State.SetWorkspaceFoldersReturnDelta(Folders);
93
94 if Added.is_empty() && Removed.is_empty() {
95 return;
96 }
97
98 if let Ok(Handle) = tokio::runtime::Handle::try_current() {
99 Handle.spawn(async move {
100 DispatchDeltaWorkspaceFolders(Added, Removed).await;
101 });
102 } else {
103 dev_log!(
104 "workspaces",
105 "warn: [LandFix:WsDelta] No tokio runtime available - delta dropped ({} added, {} removed)",
106 Added.len(),
107 Removed.len()
108 );
109 }
110}
111
112pub fn UpdateWorkspaceFoldersAndBroadcast<R:tauri::Runtime>(
117 ApplicationHandle:&tauri::AppHandle<R>,
118
119 State:&crate::ApplicationState::State::WorkspaceState::WorkspaceState::State,
120
121 Folders:Vec<WorkspaceFolderStateDTO>,
122) {
123 let (Added, Removed) = State.SetWorkspaceFoldersReturnDelta(Folders);
128
129 if Added.is_empty() && Removed.is_empty() {
130 return;
131 }
132
133 let AddedWire:Vec<serde_json::Value> = Added.iter().map(FolderToWire).collect();
134
135 let RemovedWire:Vec<serde_json::Value> = Removed.iter().map(FolderToWire).collect();
136
137 let BroadcastPayload = serde_json::json!({
138 "added": AddedWire.clone(),
139 "removed": RemovedWire.clone(),
140 "folders": State
141 .GetWorkspaceFolders()
142 .iter()
143 .map(FolderToWire)
144 .collect::<Vec<_>>(),
145 });
146
147 if let Err(Error) = LogSkyEmit(ApplicationHandle, SkyEvent::WorkspacesChanged.AsStr(), BroadcastPayload) {
148 dev_log!(
149 "workspaces",
150 "warn: [LandFix:WsDelta] sky://workspaces/changed emit failed: {}",
151 Error
152 );
153 }
154
155 PersistRecentlyOpened(&Added);
159
160 if let Ok(Handle) = tokio::runtime::Handle::try_current() {
161 Handle.spawn(async move {
162 DispatchDeltaWorkspaceFolders(Added, Removed).await;
163 });
164 }
165}
166
167fn PersistRecentlyOpened(Added:&[WorkspaceFolderStateDTO]) {
171 if Added.is_empty() {
172 return;
173 }
174
175 let Home = std::env::var("HOME")
176 .or_else(|_| std::env::var("USERPROFILE"))
177 .unwrap_or_default();
178
179 if Home.is_empty() {
180 return;
181 }
182
183 let Path = std::path::PathBuf::from(Home)
184 .join(".land")
185 .join("workspaces")
186 .join("RecentlyOpened.json");
187
188 let mut Current:serde_json::Map<String, serde_json::Value> = std::fs::read_to_string(&Path)
189 .ok()
190 .and_then(|Contents| serde_json::from_str::<serde_json::Value>(&Contents).ok())
191 .and_then(|V| V.as_object().cloned())
192 .unwrap_or_default();
193
194 let mut Workspaces = Current
195 .get("workspaces")
196 .and_then(|V| V.as_array())
197 .cloned()
198 .unwrap_or_default();
199
200 for Folder in Added {
201 let Uri = Folder.URI.to_string();
202
203 Workspaces.retain(|Entry| Entry.get("uri").and_then(|V| V.as_str()).unwrap_or("") != Uri);
204
205 Workspaces.insert(
206 0,
207 serde_json::json!({
208 "uri": Uri,
209 "label": Folder.GetDisplayName(),
210 }),
211 );
212 }
213
214 Workspaces.truncate(50);
215
216 Current.insert("workspaces".into(), serde_json::Value::Array(Workspaces));
217
218 if !Current.contains_key("files") {
219 Current.insert("files".into(), serde_json::json!([]));
220 }
221
222 if let Some(Parent) = Path.parent() {
223 let _ = std::fs::create_dir_all(Parent);
224 }
225
226 if let Ok(Serialised) = serde_json::to_vec_pretty(&serde_json::Value::Object(Current)) {
227 let _ = std::fs::write(&Path, Serialised);
228 }
229}