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