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
70		_ViewIdentifier:String,
71
72		_ItemHandle:String,
73
74		_Options:Value,
75	) -> Result<(), CommonError> {
76		Ok(())
77	}
78
79	async fn RefreshTreeView(&self, _ViewIdentifier:String, _ItemsToRefresh:Option<Value>) -> Result<(), CommonError> {
80		Ok(())
81	}
82
83	async fn SetTreeViewMessage(&self, _ViewIdentifier:String, _Message:Option<String>) -> Result<(), CommonError> {
84		Ok(())
85	}
86
87	async fn SetTreeViewTitle(
88		&self,
89
90		_ViewIdentifier:String,
91
92		_Title:Option<String>,
93
94		_Description:Option<String>,
95	) -> Result<(), CommonError> {
96		Ok(())
97	}
98
99	async fn SetTreeViewBadge(&self, _ViewIdentifier:String, _BadgeValue:Option<Value>) -> Result<(), CommonError> {
100		Ok(())
101	}
102
103	async fn OnTreeNodeExpanded(
104		&self,
105
106		_ViewIdentifier:String,
107
108		_ElementHandle:String,
109
110		_IsExpanded:bool,
111	) -> Result<(), CommonError> {
112		dev_log!("vfs", "[FileExplorer] OnTreeNodeExpanded - native provider no-op");
113
114		Ok(())
115	}
116
117	async fn OnTreeSelectionChanged(
118		&self,
119
120		_ViewIdentifier:String,
121
122		_SelectedHandles:Vec<String>,
123	) -> Result<(), CommonError> {
124		dev_log!("vfs", "[FileExplorer] OnTreeSelectionChanged - native provider no-op");
125
126		Ok(())
127	}
128
129	async fn PersistTreeViewState(&self, _ViewIdentifier:String) -> Result<Value, CommonError> {
130		Ok(json!({ "supported": false }))
131	}
132
133	async fn RestoreTreeViewState(&self, _ViewIdentifier:String, _StateValue:Value) -> Result<(), CommonError> {
134		Ok(())
135	}
136
137	// Pull methods.
138
139	async fn GetChildren(
140		&self,
141
142		_ViewIdentifier:String,
143
144		ElementHandle:Option<String>,
145	) -> Result<Vec<Value>, CommonError> {
146		let RunTime = self.AppicationHandle.state::<Arc<Runtime>>().inner().clone();
147
148		let AppState = RunTime.Environment.ApplicationState.clone();
149
150		let PathToRead = if let Some(Handle) = ElementHandle {
151			Url::parse(&Handle)
152				.map_err(|_| {
153					CommonError::InvalidArgument {
154						ArgumentName:"ElementHandle".into(),
155						Reason:"Handle is not a valid URI".into(),
156					}
157				})?
158				.to_file_path()
159				.map_err(|_| {
160					CommonError::InvalidArgument {
161						ArgumentName:"ElementHandle".into(),
162						Reason:"Handle URI is not a file path".into(),
163					}
164				})?
165		} else {
166			let Folders = AppState.Workspace.WorkspaceFolders.lock().unwrap();
167
168			let RootItems:Vec<Value> = Folders
169				.iter()
170				.map(|Folder| self.CreateTreeItemDTO(&Folder.Name, &Folder.URI, FileTypeDTO::Directory))
171				.collect();
172
173			return Ok(RootItems);
174		};
175
176		dev_log!("vfs", "[FileExplorer] GetChildren {}", PathToRead.display());
177
178		let Entries:Vec<(String, FileTypeDTO)> = RunTime.Run(ReadDirectory(PathToRead.clone())).await?;
179
180		Ok(Entries
181			.into_iter()
182			.map(|(Name, FileType)| {
183				let FullPath = PathToRead.join(&Name);
184				let URI = Url::from_file_path(FullPath).unwrap();
185				self.CreateTreeItemDTO(&Name, &URI, FileType)
186			})
187			.collect())
188	}
189
190	async fn GetTreeItem(&self, _ViewIdentifier:String, ElementHandle:String) -> Result<Value, CommonError> {
191		let URI = Url::parse(&ElementHandle).map_err(|Error| {
192			CommonError::InvalidArgument { ArgumentName:"ElementHandle".into(), Reason:Error.to_string() }
193		})?;
194
195		let Name = URI.path_segments().and_then(|S| S.last()).unwrap_or("").to_string();
196
197		let IsDirectory = URI.as_str().ends_with('/') || URI.to_file_path().map_or(false, |P| P.is_dir());
198
199		let FileType = if IsDirectory { FileTypeDTO::Directory } else { FileTypeDTO::File };
200
201		Ok(self.CreateTreeItemDTO(&Name, &URI, FileType))
202	}
203}