Skip to main content

Mountain/FileSystem/
FileExplorerViewProvider.rs

1#![allow(non_snake_case)]
2
3//! Native TreeView provider for the workspace file explorer. Implements
4//! `CommonLibrary::TreeView::TreeViewProvider`.
5//!
6//! Pull-only: `GetChildren` reads the workspace folders (when `ElementHandle`
7//! is `None`) or the directory the handle points to. `GetTreeItem` builds a
8//! single VS Code-shaped `TreeItemDTO`. Push methods are no-ops because the
9//! provider is read-only and registered directly in `ApplicationState`.
10
11use std::sync::Arc;
12
13use CommonLibrary::{
14	Effect::ApplicationRunTime::ApplicationRunTime as _,
15	Environment::Environment::Environment,
16	Error::CommonError::CommonError,
17	FileSystem::{DTO::FileTypeDTO::FileTypeDTO, ReadDirectory::ReadDirectory},
18	TreeView::TreeViewProvider::TreeViewProvider,
19};
20use async_trait::async_trait;
21use serde_json::{Value, json};
22use tauri::{AppHandle, Manager};
23use url::Url;
24
25use crate::{RunTime::ApplicationRunTime::ApplicationRunTime as Runtime, dev_log};
26
27#[derive(Clone)]
28pub struct Struct {
29	AppicationHandle:AppHandle,
30}
31
32impl Environment for Struct {}
33
34impl Struct {
35	pub fn New(AppicationHandle:AppHandle) -> Self { Self { AppicationHandle } }
36
37	fn CreateTreeItemDTO(&self, Name:&str, URI:&Url, FileType:FileTypeDTO) -> Value {
38		json!({
39			"handle": URI.to_string(),
40			"label": { "label": Name },
41			// 1 = collapsed, 0 = leaf.
42			"collapsibleState": if FileType == FileTypeDTO::Directory { 1 } else { 0 },
43			"resourceUri": json!({ "external": URI.to_string() }),
44			"command": if FileType == FileTypeDTO::File {
45				Some(json!({
46					"id": "vscode.open",
47					"title": "Open File",
48					"arguments": [json!({ "external": URI.to_string() })]
49				}))
50			} else {
51				None
52			}
53		})
54	}
55}
56
57#[async_trait]
58impl TreeViewProvider for Struct {
59	// Push methods - no-ops for native providers.
60
61	async fn RegisterTreeDataProvider(&self, _ViewIdentifier:String, _Options:Value) -> Result<(), CommonError> {
62		Ok(())
63	}
64
65	async fn UnregisterTreeDataProvider(&self, _ViewIdentifier:String) -> Result<(), CommonError> { Ok(()) }
66
67	async fn RevealTreeItem(
68		&self,
69		_ViewIdentifier:String,
70		_ItemHandle:String,
71		_Options:Value,
72	) -> Result<(), CommonError> {
73		Ok(())
74	}
75
76	async fn RefreshTreeView(&self, _ViewIdentifier:String, _ItemsToRefresh:Option<Value>) -> Result<(), CommonError> {
77		Ok(())
78	}
79
80	async fn SetTreeViewMessage(&self, _ViewIdentifier:String, _Message:Option<String>) -> Result<(), CommonError> {
81		Ok(())
82	}
83
84	async fn SetTreeViewTitle(
85		&self,
86		_ViewIdentifier:String,
87		_Title:Option<String>,
88		_Description:Option<String>,
89	) -> Result<(), CommonError> {
90		Ok(())
91	}
92
93	async fn SetTreeViewBadge(&self, _ViewIdentifier:String, _BadgeValue:Option<Value>) -> Result<(), CommonError> {
94		Ok(())
95	}
96
97	async fn OnTreeNodeExpanded(
98		&self,
99		_ViewIdentifier:String,
100		_ElementHandle:String,
101		_IsExpanded:bool,
102	) -> Result<(), CommonError> {
103		dev_log!("vfs", "[FileExplorer] OnTreeNodeExpanded - native provider no-op");
104		Ok(())
105	}
106
107	async fn OnTreeSelectionChanged(
108		&self,
109		_ViewIdentifier:String,
110		_SelectedHandles:Vec<String>,
111	) -> Result<(), CommonError> {
112		dev_log!("vfs", "[FileExplorer] OnTreeSelectionChanged - native provider no-op");
113		Ok(())
114	}
115
116	async fn PersistTreeViewState(&self, _ViewIdentifier:String) -> Result<Value, CommonError> {
117		Ok(json!({ "supported": false }))
118	}
119
120	async fn RestoreTreeViewState(&self, _ViewIdentifier:String, _StateValue:Value) -> Result<(), CommonError> {
121		Ok(())
122	}
123
124	// Pull methods.
125
126	async fn GetChildren(
127		&self,
128		_ViewIdentifier:String,
129		ElementHandle:Option<String>,
130	) -> Result<Vec<Value>, CommonError> {
131		let RunTime = self.AppicationHandle.state::<Arc<Runtime>>().inner().clone();
132		let AppState = RunTime.Environment.ApplicationState.clone();
133
134		let PathToRead = if let Some(Handle) = ElementHandle {
135			Url::parse(&Handle)
136				.map_err(|_| {
137					CommonError::InvalidArgument {
138						ArgumentName:"ElementHandle".into(),
139						Reason:"Handle is not a valid URI".into(),
140					}
141				})?
142				.to_file_path()
143				.map_err(|_| {
144					CommonError::InvalidArgument {
145						ArgumentName:"ElementHandle".into(),
146						Reason:"Handle URI is not a file path".into(),
147					}
148				})?
149		} else {
150			let Folders = AppState.Workspace.WorkspaceFolders.lock().unwrap();
151			let RootItems:Vec<Value> = Folders
152				.iter()
153				.map(|Folder| self.CreateTreeItemDTO(&Folder.Name, &Folder.URI, FileTypeDTO::Directory))
154				.collect();
155			return Ok(RootItems);
156		};
157
158		dev_log!("vfs", "[FileExplorer] GetChildren {}", PathToRead.display());
159
160		let Entries:Vec<(String, FileTypeDTO)> = RunTime.Run(ReadDirectory(PathToRead.clone())).await?;
161
162		Ok(Entries
163			.into_iter()
164			.map(|(Name, FileType)| {
165				let FullPath = PathToRead.join(&Name);
166				let URI = Url::from_file_path(FullPath).unwrap();
167				self.CreateTreeItemDTO(&Name, &URI, FileType)
168			})
169			.collect())
170	}
171
172	async fn GetTreeItem(&self, _ViewIdentifier:String, ElementHandle:String) -> Result<Value, CommonError> {
173		let URI = Url::parse(&ElementHandle).map_err(|Error| {
174			CommonError::InvalidArgument { ArgumentName:"ElementHandle".into(), Reason:Error.to_string() }
175		})?;
176
177		let Name = URI.path_segments().and_then(|S| S.last()).unwrap_or("").to_string();
178
179		let IsDirectory = URI.as_str().ends_with('/') || URI.to_file_path().map_or(false, |P| P.is_dir());
180
181		let FileType = if IsDirectory { FileTypeDTO::Directory } else { FileTypeDTO::File };
182
183		Ok(self.CreateTreeItemDTO(&Name, &URI, FileType))
184	}
185}