diff --git a/src/container.rs b/src/container.rs index a622969..9e54f44 100644 --- a/src/container.rs +++ b/src/container.rs @@ -233,7 +233,7 @@ pub fn run_command(action: ContainerAction, editor: &str) -> Result<()> { let config_path = PathBuf::from(&config_file); if config_path.exists() { let dev_container = DevContainer::from_config(&config_path, &ws.name)?; - ws.open(vec![], false, &dev_container, editor)?; + ws.open(vec![], false, &dev_container, editor, None)?; } else { ws.open_classic(vec![], false, editor)?; } diff --git a/src/launch.rs b/src/launch.rs index 4398c0d..fbdaf17 100644 --- a/src/launch.rs +++ b/src/launch.rs @@ -1,4 +1,9 @@ -use std::{ffi::OsString, fmt::Display, path::PathBuf, str::FromStr}; +use std::{ + ffi::OsString, + fmt::Display, + path::{Path, PathBuf}, + str::FromStr, +}; use clap::ValueEnum; use color_eyre::eyre::{self, Result, bail, eyre}; @@ -138,7 +143,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<&Path>, + ) -> Result> { let editor_name = format_editor_name(&self.behavior.command); match self.behavior.strategy { @@ -152,6 +161,7 @@ impl Setup { self.dry_run, dev_container, &self.behavior.command, + subfolder, )?; } else { info!("No dev container found, opening on host system with {editor_name}..."); @@ -173,6 +183,7 @@ impl Setup { self.dry_run, dev_container, &self.behavior.command, + subfolder, )?; } else { bail!( diff --git a/src/main.rs b/src/main.rs index 101ebfd..d5d9e55 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,10 +18,10 @@ mod workspace; use chrono::Utc; use clap::Parser; -use color_eyre::eyre::Result; +use color_eyre::eyre::{Result, WrapErr}; use log::trace; use std::io::Write; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use crate::config_store::ConfigStore; use crate::history::{Entry, Tracker}; @@ -52,6 +52,31 @@ fn resolve_launch_config(config: Option<&PathBuf>, store: &ConfigStore) -> Resul .transpose() } +fn workspace_root_from_config( + config: &Path, + path_arg: &Path, +) -> Result<(PathBuf, Option)> { + let abs = std::fs::canonicalize(config) + .wrap_err_with(|| format!("Config path does not exist: {}", config.display()))?; + let mut current = abs.as_path(); + let root = loop { + let Some(parent) = current.parent() else { + break abs.parent().unwrap_or(&abs).to_path_buf(); + }; + if parent.file_name().is_some_and(|n| n == ".devcontainer") { + break parent.parent().unwrap_or(parent).to_path_buf(); + } + current = parent; + }; + let path_abs = std::fs::canonicalize(path_arg).unwrap_or(path_arg.to_path_buf()); + let sub = if path_abs.starts_with(&root) && path_abs != root { + path_abs.strip_prefix(&root).ok().map(Path::to_path_buf) + } else { + None + }; + Ok((root, sub)) +} + fn main() -> Result<()> { color_eyre::install()?; @@ -70,28 +95,34 @@ fn main() -> Result<()> { match opts.command { opts::Commands::Open { path, launch } => { let mut tracker = load_tracker(opts.history_path)?; - let path = path.as_path(); - let ws = Workspace::from_path(path)?; - let ws_name = ws.name.clone(); let resolved_config = resolve_launch_config(launch.config.as_ref(), &config_store)?; let config_name = resolved_config .as_ref() .and_then(|p| config_store::config_name_from_path(p, &config_store)); + let (workspace_path, subfolder) = if let Some(ref config) = resolved_config { + workspace_root_from_config(config, &path)? + } else { + (path.clone(), None) + }; + + let ws = Workspace::from_path(&workspace_path)?; + let ws_name = ws.name.clone(); + let behavior = Behavior { strategy: launch.behavior.unwrap_or_default(), args: launch.args, command: launch.command.unwrap_or_else(|| "code".to_string()), }; let setup = Setup::new(ws, behavior.clone(), opts.dry_run); - let dev_container = setup.launch(resolved_config)?; + let dev_container = setup.launch(resolved_config, subfolder.as_deref())?; tracker.history.upsert(Entry { workspace_name: ws_name, dev_container_name: dev_container.as_ref().and_then(|dc| dc.name.clone()), config_name, - workspace_path: path.canonicalize()?, + workspace_path: workspace_path.canonicalize()?, config_path: dev_container.map(|dc| dc.config_path), behavior, last_opened: Utc::now(), @@ -130,7 +161,7 @@ fn main() -> Result<()> { .and_then(|p| config_store::config_name_from_path(p, &config_store)); let setup = Setup::new(ws, entry.behavior.clone(), opts.dry_run); - let dev_container = setup.launch(resolved_config)?; + let dev_container = setup.launch(resolved_config, None)?; tracker.history.update( id, diff --git a/src/workspace.rs b/src/workspace.rs index 334f467..503b0cb 100644 --- a/src/workspace.rs +++ b/src/workspace.rs @@ -179,14 +179,22 @@ 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") { bail!("Specifying `--folder-uri` is not possible while using vscli."); } - // 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(); + if let Some(sub) = subfolder { + let sub_str = sub.to_string_lossy().replace('\\', "/"); + if !sub_str.is_empty() && sub_str != "." { + if !container_folder.ends_with('/') { + container_folder.push('/'); + } + container_folder.push_str(&sub_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();