From 7bb8f512a67ef6157b6833065c8d20bd06cb51bd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 5 Nov 2025 08:08:17 +0000 Subject: [PATCH] fix: derive workspace from devcontainer config when using -c flag When specifying a custom devcontainer path with the -c flag, the workspace path is now automatically derived from the devcontainer config location instead of requiring the path to exist on the host filesystem. This fix addresses the issue where users couldn't open projects with custom devcontainer configurations because the workspace path validation was checking for container paths on the host filesystem. Changes: - Modified main.rs to derive workspace root from devcontainer config when -c flag is provided - Added support for container path parsing (e.g., /workspace/vscli/tests) to extract subfolder information - Updated launch.rs to accept subfolder parameter - Modified workspace.rs open() method to append subfolder to container workspace path The fix now properly handles: - Devcontainer configs in .devcontainer/ folders - Container paths like /workspace// - Relative subfolder paths - Absolute host paths relative to workspace root Fixes #82 --- src/launch.rs | 8 ++++++- src/main.rs | 56 ++++++++++++++++++++++++++++++++++++++++++++---- src/workspace.rs | 15 ++++++++++++- 3 files changed, 73 insertions(+), 6 deletions(-) diff --git a/src/launch.rs b/src/launch.rs index 712407c..20c9fd6 100644 --- a/src/launch.rs +++ b/src/launch.rs @@ -142,7 +142,11 @@ impl Setup { /// Launches vscode with the given configuration. /// Returns the dev container that was used, if any. - pub fn launch(self, config: Option) -> Result> { + pub fn launch( + self, + config: Option, + subfolder: Option, + ) -> Result> { let editor_name = format_editor_name(&self.behavior.command); match self.behavior.strategy { @@ -156,6 +160,7 @@ impl Setup { self.dry_run, dev_container, &self.behavior.command, + subfolder.as_deref(), )?; } else { info!("No dev container found, opening on host system with {editor_name}..."); @@ -177,6 +182,7 @@ impl Setup { self.dry_run, dev_container, &self.behavior.command, + subfolder.as_deref(), )?; } else { bail!( diff --git a/src/main.rs b/src/main.rs index 48aed19..3985fc8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -58,8 +58,56 @@ fn main() -> Result<()> { match opts.command { opts::Commands::Open { path, launch } => { // Get workspace from args - let path = path.as_path(); - let ws = Workspace::from_path(path)?; + // If a custom config is specified, derive workspace from config location + let (workspace_path, subfolder) = if let Some(ref config) = launch.config { + // Get the parent directory of the config file + let config_parent = config + .parent() + .ok_or_else(|| color_eyre::eyre::eyre!("Invalid config path"))?; + + // If config is in .devcontainer folder, use its parent as workspace + let workspace_root = if config_parent + .file_name() + .map(|n| n == ".devcontainer") + .unwrap_or(false) + { + config_parent + .parent() + .ok_or_else(|| { + color_eyre::eyre::eyre!("Invalid config path structure") + })? + } else { + config_parent + }; + + // Handle the path argument - it might be a container path or relative path + let path_str = path.to_string_lossy(); + let subfolder_path = if path_str.starts_with("/workspace/") || path_str.starts_with("/workspaces/") { + // Extract subfolder from container path + // e.g., "/workspace/vscli/tests" or "/workspaces/vscli/tests" + let parts: Vec<&str> = path_str.split('/').collect(); + if parts.len() > 3 { + // Skip empty, "workspace(s)", and workspace name + Some(std::path::PathBuf::from(parts[3..].join("/"))) + } else { + None + } + } else if path.is_relative() && path != std::path::Path::new(".") { + // Use relative path as-is + Some(path.to_path_buf()) + } else if path.starts_with(workspace_root) { + // Calculate relative path from workspace root + path.strip_prefix(workspace_root).ok().map(|p| p.to_path_buf()) + } else { + None + }; + + (workspace_root.to_path_buf(), subfolder_path) + } else { + (path.to_path_buf(), None) + }; + + let ws = Workspace::from_path(&workspace_path)?; let ws_name = ws.name.clone(); // Open the container @@ -69,7 +117,7 @@ fn main() -> Result<()> { command: launch.command.unwrap_or_else(|| "code".to_string()), }; let setup = Setup::new(ws, behavior.clone(), opts.dry_run); - let dev_container = setup.launch(launch.config)?; + let dev_container = setup.launch(launch.config, subfolder)?; // Store the workspace in the history tracker.history.upsert(Entry { @@ -114,7 +162,7 @@ fn main() -> Result<()> { // Open the container let setup = Setup::new(ws, entry.behavior.clone(), opts.dry_run); - let dev_container = setup.launch(entry.config_path)?; + let dev_container = setup.launch(entry.config_path, None)?; // Update the tracker entry tracker.history.update( diff --git a/src/workspace.rs b/src/workspace.rs index 00c8948..9e02132 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -192,6 +192,7 @@ impl Workspace { dry_run: bool, dev_container: &DevContainer, command: &str, + subfolder: Option<&Path>, ) -> Result<()> { // Checking if '--folder-uri' is present in the arguments if args.iter().any(|arg| arg == "--folder-uri") { @@ -199,7 +200,19 @@ impl Workspace { } // get the folder path from the selected dev container - let container_folder: String = dev_container.workspace_path_in_container.clone(); + let mut container_folder: String = dev_container.workspace_path_in_container.clone(); + + // Append subfolder if specified + if let Some(subfolder) = subfolder { + let subfolder_str = subfolder.to_string_lossy(); + if !subfolder_str.is_empty() && subfolder_str != "." { + // Ensure proper path separator + if !container_folder.ends_with('/') { + container_folder.push('/'); + } + container_folder.push_str(&subfolder_str); + } + } let mut ws_path: String = self.path.to_string_lossy().into_owned(); let mut dc_path: String = dev_container.config_path.to_string_lossy().into_owned();