Skip to main content

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