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}