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}