From c0feac21dba3fcc3c879434ae2f807cc668083b9 Mon Sep 17 00:00:00 2001 From: Faizan Qureshi Date: Wed, 29 Apr 2026 04:30:24 +0530 Subject: [PATCH] Expand `~` in `warp://action/new_tab?path=` URLs (#9277) ## Description Fixes #9178. `warp://action/new_tab?path=~/foo` was opening the current directory instead of `~/foo`. The query value was being passed straight into `PathBuf::from_str`, which doesn't expand `~`, so the resulting literal `~/foo` couldn't be resolved by `open_file` and the new tab silently fell back to the current location. Routed the `path` query through `shellexpand::tilde` (already a workspace dependency) before constructing the `PathBuf`. The same call site serves both `/new_tab` and `/new_window`, so both URLs benefit. Absolute and relative paths are unchanged. ## Testing Added 6 unit tests in `app/src/uri/uri_test.rs`: - `~/Projects` expands to `$HOME/Projects` - URL-encoded `%7E%2FProjects` expands the same way - absolute path `/tmp/foo` unchanged - relative path `relative/dir` unchanged - missing `path=` returns `None` - bare `~` expands to `$HOME` Also verified the helper in a standalone harness against extra edge cases: empty value, non-leading `~` (unchanged, correct shell semantics), `~user/path` (unchanged, a shellexpand limitation), and URL-encoded spaces. Couldn't run the in-tree test suite locally because the Metal toolchain isn't installed, so relying on CI for the full clippy / nextest pass. ## Agent Mode - [ ] Warp Agent Mode - This PR was created via Warp's AI Agent Mode ## Changelog Entries for Stable CHANGELOG-BUG-FIX: `warp://action/new_tab?path=~/foo` (and `/new_window`) now expand `~` to your home directory. --------- Co-authored-by: seemeroland --- app/src/uri/mod.rs | 13 ++++++++----- app/src/uri/uri_test.rs | 40 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 5 deletions(-) diff --git a/app/src/uri/mod.rs b/app/src/uri/mod.rs index 023dadf1..700cb8db 100644 --- a/app/src/uri/mod.rs +++ b/app/src/uri/mod.rs @@ -657,6 +657,13 @@ fn find_matching_config_name<'a>( .find(|&config| config.name.to_lowercase() == target_name_lower) } +/// Extract the `path` query parameter, expanding a leading `~` to the +/// user's home directory. +fn parse_tab_path(url: &Url) -> Option { + let raw = url.query_pairs().find(|(k, _)| k == "path")?.1; + Some(PathBuf::from(shellexpand::tilde(&raw).into_owned())) +} + #[derive(Debug)] enum Action { NewTab, @@ -706,11 +713,7 @@ impl Action { } else { None }; - let Some(Ok(path)) = url - .query_pairs() - .find(|(k, _v)| k == "path") - .map(|(_, path)| PathBuf::from_str(&path)) - else { + let Some(path) = parse_tab_path(url) else { log::warn!("Could not parse path to open a new tab/window"); return; }; diff --git a/app/src/uri/uri_test.rs b/app/src/uri/uri_test.rs index e1586464..a4d0df78 100644 --- a/app/src/uri/uri_test.rs +++ b/app/src/uri/uri_test.rs @@ -533,3 +533,43 @@ fn validate_custom_uri_errors_do_not_leak_query_string() { assert!(!msg.contains("refresh_token"), "{msg}"); assert!(!msg.contains("LEAKED"), "{msg}"); } + +#[test] +fn test_parse_tab_path_expands_tilde() { + let url = Url::parse("warp://action/new_tab?path=~/Projects").unwrap(); + let home = dirs::home_dir().expect("HOME must be set for this test"); + assert_eq!(parse_tab_path(&url), Some(home.join("Projects"))); +} + +#[test] +fn test_parse_tab_path_expands_url_encoded_tilde() { + // `%7E` and `%2F` are URL-encoded `~` and `/`. + let url = Url::parse("warp://action/new_tab?path=%7E%2FProjects").unwrap(); + let home = dirs::home_dir().expect("HOME must be set for this test"); + assert_eq!(parse_tab_path(&url), Some(home.join("Projects"))); +} + +#[test] +fn test_parse_tab_path_absolute_path_unchanged() { + let url = Url::parse("warp://action/new_tab?path=/tmp/foo").unwrap(); + assert_eq!(parse_tab_path(&url), Some(PathBuf::from("/tmp/foo"))); +} + +#[test] +fn test_parse_tab_path_relative_path_unchanged() { + let url = Url::parse("warp://action/new_tab?path=relative/dir").unwrap(); + assert_eq!(parse_tab_path(&url), Some(PathBuf::from("relative/dir"))); +} + +#[test] +fn test_parse_tab_path_missing_returns_none() { + let url = Url::parse("warp://action/new_tab").unwrap(); + assert_eq!(parse_tab_path(&url), None); +} + +#[test] +fn test_parse_tab_path_bare_tilde() { + let url = Url::parse("warp://action/new_tab?path=~").unwrap(); + let home = dirs::home_dir().expect("HOME must be set for this test"); + assert_eq!(parse_tab_path(&url), Some(home)); +}