Skip to main content

Mountain/FileSystem/
FileExplorerViewProvider.rs

1//! # FileExplorerViewProvider (FileSystem)
2//!
3//! A native (Rust-implemented) `TreeViewProvider` that provides the data for
4//! the file explorer (tree) view in Mountain. This is a **native provider**,
5//! meaning it is implemented directly in Rust rather than being provided by an
6//! extension.
7//!
8//! ## RESPONSIBILITIES
9//!
10//! ### 1. Root-Level Items (Workspace Folders)
11//! - Return the list of workspace folders as root tree nodes
12//! - Each folder appears as a collapsible node at the top level
13//! - Folder names are displayed as labels
14//!
15//! ### 2. Directory Listing
16//! - Provide children for a given directory URI (via `GetChildren`)
17//! - Read filesystem to enumerate files and subdirectories
18//! - Return appropriate `TreeItemDTO` for each entry
19//! - Handle permissions errors gracefully
20//!
21//! ### 3. Tree Item Construction
22//! - Build `TreeItemDTO` JSON objects with proper structure:
23//! - `handle`: Unique identifier (file URI)
24//! - `label`: Display name
25//! - `collapsibleState`: 1 for directories, 0 for files
26//! - `resourceUri`: File URI with `external` property
27//! - `command`: Open file command for leaf nodes
28//! ## ARCHITECTURAL ROLE
29//!
30//! The FileExplorerViewProvider is a **native TreeViewProvider**:
31//!
32//! ```text
33//! TreeView API ──► FileExplorerViewProvider ──► FileSystem ReadDirectory/ReadFile
34//!                          │
35//!                          └─► Returns TreeItemDTO JSON
36//! ```
37//!
38//! ### Position in Mountain
39//! - `FileSystem` module: File system operations
40//! - Implements `CommonLibrary::TreeView::TreeViewProvider` trait
41//! - Registered as provider in `ApplicationState::ActiveTreeViews`
42//!
43//! ### Differences from Extension Providers
44//! - **Native Provider**: Direct Rust implementation, no extension hosting
45//! - **Read-Only**: Only implements "pull" methods (`GetChildren`,
46//!   `GetTreeItem`)
47//! - **No Push Methods**: Does not use `RegisterTreeDataProvider`,
48//!   `RefreshTreeView`, etc.
49//! - **No Sidecar**: No extension host communication overhead
50//!
51//! ### Dependencies
52//! - `CommonLibrary::FileSystem::ReadDirectory` and `ReadFile`: Filesystem
53//!   access
54//! - `CommonLibrary::TreeView::TreeViewProvider`: Provider trait
55//! - `ApplicationRunTime`: Effect execution
56//! - `ApplicationState`: Workspace folder access
57//!
58//! ### Dependents
59//! - `Binary::Main::Fn`: Creates and registers provider instance
60//! - TreeView UI component: Requests data via provider methods
61//! - Command handlers: Trigger tree view operations
62//!
63//! ## TREE ITEM DTO STRUCTURE
64//!
65//! Each tree item is a JSON object compatible with VS Code's `TreeItem`:
66//!
67//! ```json
68//! {
69//!   "handle": "file:///path/to/item",
70//!   "label": { "label": "itemName" },
71//!   "collapsibleState": 1,
72//!   "resourceUri": { "external": "file:///path/to/item" },
73//!   "command": {
74//!     "id": "vscode.open",
75//!     "title": "Open File",
76//!     "arguments": [{ "external": "file:///path/to/item" }]
77//!   }
78//! }
79//! ```
80//!
81//! ## METHODS OVERVIEW
82//!
83//! - `GetChildren`: Returns child items for a given parent directory
84//! - `GetTreeItem`: Returns a single tree item for a given handle (URI)
85//! - Other `TreeViewProvider` methods (push-based) are no-ops for native
86//!   providers
87//!
88//! ## ERROR HANDLING
89//!
90//! - Filesystem errors are converted to `CommonError::FileSystemIO`
91//! - Invalid URIs return `CommonError::InvalidArgument`
92//! - Permission errors are logged and empty results returned
93//!
94//! ## PERFORMANCE
95//!
96//! - Directory reads are async via `ApplicationRunTime`
97//! - Each `GetChildren` call reads the directory from disk
98//! - Consider caching for large directories (TODO)
99//! - Stat calls are minimized by using directory entry metadata
100//!
101//! ## VS CODE REFERENCE
102//!
103//! Patterns from VS Code:
104//! - `vs/workbench/contrib/files/browser/filesViewProvider.ts`: File tree
105//!   provider
106//! - `vs/platform/workspace/common/workspace.ts`: Tree item DTO structure
107//!
108//! ## TODO
109//!
110//! - [ ] Implement tree item caching for better performance
111//! - [ ] Add file icon decoration based on file type
112//! - [ ] Support drag-and-drop operations
113//! - [ ] Add file/folder filtering (gitignore, exclude patterns)
114//! - [ ] Implement tree state persistence (expanded/collapsed)
115//! - [ ] Add file change notifications (watch for file system events)
116//! - [ ] Support virtual workspace folders (non-file URIs)
117//!
118//! ## MODULE CONTENTS
119//!
120//! - [`FileExplorerViewProvider`]: Main provider struct
121//! - `CreateTreeItemDTO`: Helper to build tree item JSON
122
123use std::sync::Arc;
124
125use CommonLibrary::{
126	Effect::{ApplicationRunTime, ApplicationRunTime::ApplicationRunTime as ApplicationRunTimeTrait},
127	Environment::Environment::Environment,
128	Error::CommonError::CommonError,
129	FileSystem::{DTO::FileTypeDTO::FileTypeDTO, ReadDirectory::ReadDirectory},
130	TreeView::TreeViewProvider::TreeViewProvider,
131};
132use async_trait::async_trait;
133use serde_json::{Value, json};
134// Import AppHandle and Manager trait
135use tauri::{AppHandle, Manager};
136use url::Url;
137
138use crate::RunTime::ApplicationRunTime::ApplicationRunTime as Runtime;
139use crate::dev_log;
140
141#[derive(Clone)]
142pub struct FileExplorerViewProvider {
143	AppicationHandle:AppHandle,
144}
145
146impl Environment for FileExplorerViewProvider {}
147
148impl FileExplorerViewProvider {
149	pub fn New(AppicationHandle:AppHandle) -> Self { Self { AppicationHandle } }
150
151	// Helper function to create the DTO, merged with V2's format
152	fn CreateTreeItemDTO(&self, Name:&str, Uri:&Url, FileType:FileTypeDTO) -> Value {
153		json!({
154
155					"handle": Uri.to_string(),
156
157					"label": { "label": Name },
158
159		// 1: Collapsed, 0: None
160					"collapsibleState": if FileType == FileTypeDTO::Directory { 1 } else { 0 },
161
162					"resourceUri": json!({ "external": Uri.to_string() }),
163
164					"command": if FileType == FileTypeDTO::File {
165
166						Some(json!({
167
168							"id": "vscode.open",
169
170							"title": "Open File",
171
172							"arguments": [json!({ "external": Uri.to_string() })]
173						}))
174					} else {
175
176						None
177					}
178
179				})
180	}
181}
182
183#[async_trait]
184impl TreeViewProvider for FileExplorerViewProvider {
185	// --- PUSH methods (not used by native providers) ---
186	async fn RegisterTreeDataProvider(&self, _ViewIdentifier:String, _Options:Value) -> Result<(), CommonError> {
187		Ok(())
188	}
189
190	async fn UnregisterTreeDataProvider(&self, _ViewIdentifier:String) -> Result<(), CommonError> { Ok(()) }
191
192	async fn RevealTreeItem(
193		&self,
194
195		_ViewIdentifier:String,
196
197		_ItemHandle:String,
198
199		_Options:Value,
200	) -> Result<(), CommonError> {
201		Ok(())
202	}
203
204	async fn RefreshTreeView(&self, _ViewIdentifier:String, _ItemsToRefresh:Option<Value>) -> Result<(), CommonError> {
205		Ok(())
206	}
207
208	async fn SetTreeViewMessage(&self, _ViewIdentifier:String, _Message:Option<String>) -> Result<(), CommonError> {
209		Ok(())
210	}
211
212	async fn SetTreeViewTitle(
213		&self,
214
215		_ViewIdentifier:String,
216
217		_Title:Option<String>,
218
219		_Description:Option<String>,
220	) -> Result<(), CommonError> {
221		Ok(())
222	}
223
224	async fn SetTreeViewBadge(&self, _ViewIdentifier:String, _BadgeValue:Option<Value>) -> Result<(), CommonError> {
225		Ok(())
226	}
227
228	// --- State Management Methods (not used by native file explorer providers) ---
229
230	/// Handles tree node expansion/collapse events.
231	/// These events are not relevant for the native file explorer provider.
232	async fn OnTreeNodeExpanded(
233		&self,
234		_ViewIdentifier:String,
235		_ElementHandle:String,
236		_IsExpanded:bool,
237	) -> Result<(), CommonError> {
238		dev_log!("vfs", "[FileExplorer] OnTreeNodeExpanded called - not implemented for native providers");
239		Ok(())
240	}
241
242	/// Handles tree selection changes.
243	/// These events are not relevant for the native file explorer provider.
244	async fn OnTreeSelectionChanged(
245		&self,
246		_ViewIdentifier:String,
247		_SelectedHandles:Vec<String>,
248	) -> Result<(), CommonError> {
249		dev_log!("vfs", "[FileExplorer] OnTreeSelectionChanged called - not implemented for native providers");
250		Ok(())
251	}
252
253	/// Persists tree view state.
254	/// These events are not relevant for the native file explorer provider.
255	async fn PersistTreeViewState(&self, _ViewIdentifier:String) -> Result<Value, CommonError> {
256		dev_log!("vfs", "[FileExplorer] PersistTreeViewState called - not implemented for native providers");
257		Ok(json!({ "supported": false }))
258	}
259
260	/// Restores tree view state.
261	/// These events are not relevant for the native file explorer provider.
262	async fn RestoreTreeViewState(&self, _ViewIdentifier:String, _StateValue:Value) -> Result<(), CommonError> {
263		dev_log!("vfs", "[FileExplorer] RestoreTreeViewState called - not implemented for native providers");
264		Ok(())
265	}
266
267	// --- PULL methods (implemented by native providers) ---
268
269	/// Retrieves the children for a given directory URI.
270	async fn GetChildren(
271		&self,
272
273		// Kept for trait signature compatibility, but unused.
274		_ViewIdentifier:String,
275
276		ElementHandle:Option<String>,
277	) -> Result<Vec<Value>, CommonError> {
278		let RunTime = self.AppicationHandle.state::<Arc<Runtime>>().inner().clone();
279
280		let AppState = RunTime.Environment.ApplicationState.clone();
281
282		let PathToRead = if let Some(Handle) = ElementHandle {
283			// If an element is provided, it's a directory URI string.
284			Url::parse(&Handle)
285				.map_err(|_| {
286					CommonError::InvalidArgument {
287						ArgumentName:"ElementHandle".into(),
288
289						Reason:"Handle is not a valid URI".into(),
290					}
291				})?
292				.to_file_path()
293				.map_err(|_| {
294					CommonError::InvalidArgument {
295						ArgumentName:"ElementHandle".into(),
296
297						Reason:"Handle URI is not a file path".into(),
298					}
299				})?
300		} else {
301			// If no element, we are at the root. We should return the workspace folders.
302			let Folders = AppState.Workspace.WorkspaceFolders.lock().unwrap();
303
304			let RootItems:Vec<Value> = Folders
305				.iter()
306				.map(|folder| self.CreateTreeItemDTO(&folder.Name, &folder.URI, FileTypeDTO::Directory))
307				.collect();
308
309			return Ok(RootItems);
310		};
311
312		dev_log!("vfs", "[FileExplorer] Getting children for path: {}", PathToRead.display());
313
314		// This now works because `RunTime` has the correct type and implements the
315		// `ApplicationRunTime` trait.
316		let Entries:Vec<(String, CommonLibrary::FileSystem::DTO::FileTypeDTO::FileTypeDTO)> =
317			RunTime.Run(ReadDirectory(PathToRead.clone())).await?;
318
319		let Items = Entries
320			.into_iter()
321			.map(|(Name, FileType)| {
322				let FullPath = PathToRead.join(&Name);
323
324				let Uri = Url::from_file_path(FullPath).unwrap();
325
326				self.CreateTreeItemDTO(&Name, &Uri, FileType)
327			})
328			.collect();
329
330		Ok(Items)
331	}
332
333	/// Retrieves the `TreeItem` for a given handle (which is its URI).
334	async fn GetTreeItem(&self, _ViewIdentifier:String, ElementHandle:String) -> Result<Value, CommonError> {
335		let URI = Url::parse(&ElementHandle).map_err(|Error| {
336			CommonError::InvalidArgument { ArgumentName:"ElementHandle".into(), Reason:Error.to_string() }
337		})?;
338
339		let Name = URI.path_segments().and_then(|s| s.last()).unwrap_or("").to_string();
340
341		// Use robust check from V1
342		let IsDirectory = URI.as_str().ends_with('/') || URI.to_file_path().map_or(false, |p| p.is_dir());
343
344		let FileType = if IsDirectory { FileTypeDTO::Directory } else { FileTypeDTO::File };
345
346		Ok(self.CreateTreeItemDTO(&Name, &URI, FileType))
347	}
348}