Skip to main content

Mountain/Track/Effect/CreateEffectForRequest/
TreeView.rs

1#![allow(non_snake_case, unused_variables, dead_code, unused_imports)]
2
3//! # TreeView Effect (CreateEffectForRequest)
4//!
5//! Effect constructors for tree-view registration and disposal from the
6//! Cocoon extension host. Delegates to `TreeViewProvider` on
7//! `MountainEnvironment` and emits `SkyEvent` notifications to keep the
8//! Sky workbench's `ITreeView` instances in sync.
9//!
10//! ## Methods handled
11//!
12//! | Method | Description |
13//! |---|---|
14//! | `$tree:register` / `tree.register` | Register a tree data provider for a view ID |
15//! | `tree.unregister` / `tree.dispose` | Unregister and dispose a tree data provider |
16//!
17//! After a successful registration, a `TreeViewCreate` event is emitted so
18//! Sky can wire the new data provider. After disposal, a `TreeViewDispose`
19//! event clears the stale provider reference.
20
21use std::{future::Future, pin::Pin, sync::Arc};
22
23use CommonLibrary::{
24	Environment::Requires::Requires,
25	IPC::SkyEvent::SkyEvent,
26	TreeView::TreeViewProvider::TreeViewProvider,
27};
28use serde_json::{Value, json};
29use tauri::Runtime;
30
31use crate::{
32	IPC::SkyEmit::LogSkyEmit,
33	RunTime::ApplicationRunTime::ApplicationRunTime,
34	Track::Effect::MappedEffectType::MappedEffect,
35	dev_log,
36};
37
38pub fn CreateEffect<R:Runtime>(MethodName:&str, Parameters:Value) -> Option<Result<MappedEffect, String>> {
39	match MethodName {
40		"$tree:register" | "tree.register" => {
41			let DispatchEnterNs = std::time::SystemTime::now()
42				.duration_since(std::time::UNIX_EPOCH)
43				.map(|D| D.as_nanos())
44				.unwrap_or(0);
45
46			dev_log!(
47				"tree-latency",
48				"[LandFix:Tree] dispatch-enter method={} t_ns={}",
49				MethodName,
50				DispatchEnterNs
51			);
52
53			let effect =
54				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
55					Box::pin(async move {
56						let DispatchAt = std::time::Instant::now();
57						let BodyStartNs = std::time::SystemTime::now()
58							.duration_since(std::time::UNIX_EPOCH)
59							.map(|D| D.as_nanos())
60							.unwrap_or(0);
61						let provider:Arc<dyn TreeViewProvider> = run_time.Environment.Require();
62						let first = Parameters.get(0).and_then(Value::as_str).unwrap_or("");
63						let (view_id, options) = if Parameters.get(2).is_some() {
64							let vid = Parameters.get(1).and_then(Value::as_str).unwrap_or(first).to_string();
65							let opts = Parameters.get(2).cloned().unwrap_or_default();
66							(vid, opts)
67						} else {
68							let vid = first.to_string();
69							let opts = Parameters.get(1).cloned().unwrap_or_default();
70							(vid, opts)
71						};
72						let ViewIdForLog = view_id.clone();
73						dev_log!("grpc", "[LandFix:Tree] body-start view={} t_ns={}", ViewIdForLog, BodyStartNs);
74						let Result = provider.RegisterTreeDataProvider(view_id.clone(), options.clone()).await;
75						let RegisteredNs = std::time::SystemTime::now()
76							.duration_since(std::time::UNIX_EPOCH)
77							.map(|D| D.as_nanos())
78							.unwrap_or(0);
79						dev_log!(
80							"grpc",
81							"[LandFix:Tree] registered view={} elapsed={}ms t_ns={}",
82							ViewIdForLog,
83							DispatchAt.elapsed().as_millis(),
84							RegisteredNs
85						);
86						dev_log!(
87							"tree-view",
88							"[TreeView] register view={} result={} elapsed={}ms",
89							ViewIdForLog,
90							if Result.is_ok() { "ok" } else { "err" },
91							DispatchAt.elapsed().as_millis()
92						);
93
94						// Notify Wind/Sky that a data provider now exists for this
95						// view, so the renderer can set `treeView.dataProvider` on
96						// the matching ITreeView instance and replace the default
97						// "no data provider registered" message. Without this
98						// emit, `vs/workbench/browser/parts/views/treeView.ts`
99						// keeps `_dataProvider === undefined` and every extension
100						// tree view stays empty (GitLens, debug, SCM, tasks, etc.).
101						if Result.is_ok() {
102							// Routes through `LogSkyEmit` so every emit also
103							// surfaces under the `sky-emit` tag in addition to
104							// the existing `tree-view` diagnostic. Failure
105							// reason stays surfaced via both tags.
106							match LogSkyEmit(
107								&run_time.Environment.ApplicationHandle,
108								SkyEvent::TreeViewCreate.AsStr(),
109								json!({
110									"viewId": view_id,
111									"options": options,
112								}),
113							) {
114								Ok(()) => {
115									dev_log!(
116										"tree-view",
117										"[TreeView] emit-ok channel={} view={}",
118										SkyEvent::TreeViewCreate.AsStr(),
119										ViewIdForLog
120									);
121								},
122								Err(Error) => {
123									dev_log!(
124										"grpc",
125										"warn: [LandFix:Tree] failed to emit {} for view={}: {}",
126										SkyEvent::TreeViewCreate.AsStr(),
127										ViewIdForLog,
128										Error
129									);
130									dev_log!(
131										"tree-view",
132										"[TreeView] emit-fail channel={} view={} error={}",
133										SkyEvent::TreeViewCreate.AsStr(),
134										ViewIdForLog,
135										Error
136									);
137								},
138							}
139						}
140
141						Result.map(|_| json!(null)).map_err(|e| e.to_string())
142					})
143				};
144
145			Some(Ok(Box::new(effect)))
146		},
147
148		"tree.unregister" | "tree.dispose" => {
149			let effect =
150				move |run_time:Arc<ApplicationRunTime>| -> Pin<Box<dyn Future<Output = Result<Value, String>> + Send>> {
151					Box::pin(async move {
152						let view_id = Parameters.get(0).and_then(Value::as_str).unwrap_or("").to_string();
153						dev_log!("tree-view", "[TreeView] unregister view={}", view_id);
154						if view_id.is_empty() {
155							dev_log!("tree-view", "[TreeView] unregister skipped: empty view_id");
156							return Ok(json!(null));
157						}
158						let provider:Arc<dyn TreeViewProvider> = run_time.Environment.Require();
159						let Result = provider.UnregisterTreeDataProvider(view_id.clone()).await;
160						dev_log!(
161							"tree-view",
162							"[TreeView] unregister result={} view={}",
163							if Result.is_ok() { "ok" } else { "err" },
164							view_id
165						);
166						// Emit TreeViewDispose so Sky's
167						// `_dataProvider` slot is cleared on the
168						// renderer side. Without this emit, Sky keeps
169						// the stale provider reference alive and
170						// `getChildren` continues to hit it after
171						// the extension has disposed the registration.
172						if Result.is_ok() {
173							match LogSkyEmit(
174								&run_time.Environment.ApplicationHandle,
175								SkyEvent::TreeViewDispose.AsStr(),
176								json!({ "viewId": view_id }),
177							) {
178								Ok(()) => {
179									dev_log!(
180										"tree-view",
181										"[TreeView] dispose-emit-ok channel={} view={}",
182										SkyEvent::TreeViewDispose.AsStr(),
183										view_id
184									);
185								},
186								Err(Error) => {
187									dev_log!(
188										"grpc",
189										"warn: [LandFix:Tree] failed to emit {} for view={}: {}",
190										SkyEvent::TreeViewDispose.AsStr(),
191										view_id,
192										Error
193									);
194								},
195							}
196						}
197						Result.map(|_| json!(null)).map_err(|e| e.to_string())
198					})
199				};
200
201			Some(Ok(Box::new(effect)))
202		},
203
204		_ => None,
205	}
206}