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}