Mountain/Workspace/WorkspaceFileService.rs
1//! # WorkspaceFileService (Workspace)
2//!
3//! RESPONSIBILITIES:
4//! - Parses `.code-workspace` configuration files (VSCode multi-root workspace
5//! format)
6//! - Resolves relative folder paths to absolute filesystem paths
7//! - Converts parsed workspace folders into `WorkspaceFolderStateDTO` instances
8//! - Handles path canonicalization and URI conversion
9//!
10//! ARCHITECTURAL ROLE:
11//! - Utility module for workspace configuration management
12//! - Used by [`MountainEnvironment`](crate::Environment::MountainEnvironment)
13//! during workspace initialization and configuration loading
14//! - Integrates with
15//! [`ApplicationState`](crate::ApplicationState::ApplicationState) for
16//! workspace folder state management
17//!
18//! FILE FORMAT:
19//! - Expects JSON format conforming to VSCode `.code-workspace` schema
20//! - Top-level object contains `folders` array (can also have `settings`,
21//! `extensions`)
22//! - Each folder entry has at least a `path` field (relative to workspace file)
23//! - Example: `{"folders": [{"path": "."}, {"path": "../other-project"}]}`
24//!
25//! PARSING FLOW:
26//! 1. Read `.code-workspace` file content as string
27//! 2. Deserialize JSON into `WorkspaceFile` struct (using serde)
28//! 3. Determine workspace file's parent directory (base for relative paths)
29//! 4. For each folder entry:
30//! - Join relative path with base directory
31//! - Canonicalize to resolve symlinks and relative segments (`..`, `.`)
32//! - Convert absolute path to `file://` URI via `Url::from_directory_path`
33//! - Extract folder name from path (fallback to "untitled-folder")
34//! - Assign sequential index based on declaration order
35//! 5. Return `Vec<WorkspaceFolderStateDTO>` with all resolved folders
36//!
37//! ERROR HANDLING:
38//! - Returns [`CommonError`](CommonLibrary::Error::CommonError) on any failure
39//! - JSON deserialization errors → `SerializationError`
40//! - Missing parent directory → `FileSystemIO`
41//! - Path canonicalization failure → `FileSystemNotFound`
42//! - URI conversion failure → `InvalidArgument`
43//!
44//! PERFORMANCE:
45//! - Synchronous function but should be called from async context
46//! - Each folder path undergoes I/O: join + canonicalize (can be slow on
47//! network drives)
48//! - Consider caching parsed workspace files if frequently accessed
49//!
50//! VS CODE REFERENCE:
51//! - `vs/workbench/workspaces/common/workspace.ts` - workspace file format
52//! - `vs/workbench/services/workspace/browser/workspaceService.ts` - workspace
53//! service
54//! - `vs/platform/workspace/common/workspace.ts` - workspace interfaces
55//!
56//! TODO:
57//! - Add validation for workspace file schema version
58//! - Support workspace file `settings` section (merge into configuration)
59//! - Parse workspace `extensions` section (recommend extensions)
60//! - Add support for workspace file glob patterns in folder paths
61//! - Implement workspace file change watching (reload on external edits)
62//! - Add workspace file templates for common multi-root configurations
63//! - Support workspace file encryption for sensitive paths
64//! - Handle workspace files on remote filesystems (SSH, containers)
65//! - Add workspace file migration tools (format upgrades)
66//! - Implement workspace file validation and linting
67//! - Support workspace file includes (composite workspaces)
68//!
69//! MODULE CONTENTS:
70//! - Structs: `WorkspaceFile`, `WorkspaceFolderEntry` (serde deserialization)
71//! - Function: `ParseWorkspaceFile` - main entry point
72//! - Data type:
73//! [`WorkspaceFolderStateDTO`](crate::ApplicationState::DTO::WorkspaceFolderStateDTO)
74
75use std::path::Path;
76
77use CommonLibrary::Error::CommonError::CommonError;
78use serde::Deserialize;
79use url::Url;
80
81use crate::ApplicationState::DTO::WorkspaceFolderStateDTO::WorkspaceFolderStateDTO;
82
83#[derive(Deserialize, Debug)]
84struct WorkspaceFile {
85 folders:Vec<WorkspaceFolderEntry>,
86 // Can also contain 'settings', 'extensions', etc.
87}
88
89#[derive(Deserialize, Debug)]
90struct WorkspaceFolderEntry {
91 path:String,
92}
93
94/// Parses a `.code-workspace` file content and resolves the folder paths.
95///
96/// # Parameters
97/// * `WorkspaceFilePath`: The absolute path to the `.code-workspace` file.
98/// * `FileContent`: The raw string content of the file.
99///
100/// # Returns
101/// A `Result` containing a vector of `WorkspaceFolderStateDTO`s.
102pub fn ParseWorkspaceFile(
103 WorkspaceFilePath:&Path,
104
105 FileContent:&str,
106) -> Result<Vec<WorkspaceFolderStateDTO>, CommonError> {
107 let Parsed:WorkspaceFile = serde_json::from_str(FileContent)
108 .map_err(|Error| CommonError::SerializationError { Description:Error.to_string() })?;
109
110 let WorkspaceFileDirectory = WorkspaceFilePath.parent().ok_or_else(|| {
111 CommonError::FileSystemIO {
112 Path:WorkspaceFilePath.to_path_buf(),
113
114 Description:"Cannot get parent directory of workspace file".to_string(),
115 }
116 })?;
117
118 let Folders:Result<Vec<WorkspaceFolderStateDTO>, CommonError> = Parsed
119 .folders
120 .into_iter()
121 .enumerate()
122 .map(|(Index, Entry)| {
123 let FolderPath = WorkspaceFileDirectory.join(Entry.path);
124
125 let CanonicalPath = FolderPath
126 .canonicalize()
127 .map_err(|_| CommonError::FileSystemNotFound(FolderPath.clone()))?;
128
129 let FolderURI = Url::from_directory_path(&CanonicalPath).map_err(|_| {
130 CommonError::InvalidArgument {
131 ArgumentName:"path".into(),
132
133 Reason:format!("Could not convert path '{}' to URL", CanonicalPath.display()),
134 }
135 })?;
136
137 let Name = CanonicalPath
138 .file_name()
139 .and_then(|n| n.to_str())
140 .unwrap_or("untitled-folder")
141 .to_string();
142
143 Ok(WorkspaceFolderStateDTO { URI:FolderURI, Name, Index })
144 })
145 .collect();
146
147 Folders
148}