Skip to main content

Mountain/FileSystem/
FileExplorerViewProvider.rs

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