Rebrand to Galaxy, major improvements to Bedrock support, still needs some TLC though

This commit is contained in:
Ryan Ward
2026-05-07 11:29:34 -05:00
parent f4e2475c60
commit a41cbd8cc7
2433 changed files with 14208 additions and 9409 deletions
+810
View File
@@ -0,0 +1,810 @@
use super::*;
#[test]
fn test_user_friendly_path_with_home() {
let home = "/Users/blue";
assert_eq!(
user_friendly_path("/Users/blue", Some(home)),
"~".to_string(),
);
assert_eq!(
user_friendly_path("/Users/blue/warp", Some(home)),
"~/warp".to_string(),
);
assert_eq!(
user_friendly_path("/Users/admin/warp", Some(home)),
"/Users/admin/warp".to_string(),
);
}
#[test]
fn test_to_relative_path() {
use super::to_relative_path;
use std::path::Path;
// Basic relative path conversion
#[cfg(not(windows))]
{
assert_eq!(
to_relative_path(
false,
Path::new("/Users/john/projects/app/src/main.rs"),
Path::new("/Users/john/projects")
),
Some("app/src/main.rs".to_string())
);
// Same directory
assert_eq!(
to_relative_path(
false,
Path::new("/Users/john/projects"),
Path::new("/Users/john/projects")
),
Some(".".to_string())
);
// Parent directory
assert_eq!(
to_relative_path(
false,
Path::new("/Users/john"),
Path::new("/Users/john/projects")
),
Some("..".to_string())
);
// Nested parent
assert_eq!(
to_relative_path(
false,
Path::new("/Users"),
Path::new("/Users/john/projects")
),
Some("../..".to_string())
);
// Cross-branch paths
assert_eq!(
to_relative_path(
false,
Path::new("/Users/john/documents/file.txt"),
Path::new("/Users/john/projects")
),
Some("../documents/file.txt".to_string())
);
// Root to subdirectory
assert_eq!(
to_relative_path(false, Path::new("/var/log/system.log"), Path::new("/")),
Some("var/log/system.log".to_string())
);
// Handles paths that would have leading slashes correctly
assert_eq!(
to_relative_path(false, Path::new("/home/user/file.txt"), Path::new("/home")),
Some("user/file.txt".to_string())
);
// Test with current directory references that should be cleaned
assert_eq!(
to_relative_path(
false,
Path::new("/Users/john/projects/./app/src/main.rs"),
Path::new("/Users/john/projects")
),
Some("app/src/main.rs".to_string()),
);
}
#[cfg(windows)]
{
assert_eq!(
to_relative_path(
false,
Path::new("/Users/john/projects/app/src/main.rs"),
Path::new("/Users/john/projects")
),
Some("app\\src\\main.rs".to_string())
);
// Same directory
assert_eq!(
to_relative_path(
false,
Path::new("/Users/john/projects"),
Path::new("/Users/john/projects")
),
Some(".".to_string())
);
// Parent directory
assert_eq!(
to_relative_path(
false,
Path::new("/Users/john"),
Path::new("/Users/john/projects")
),
Some("..".to_string())
);
// Nested parent
assert_eq!(
to_relative_path(
false,
Path::new("/Users"),
Path::new("/Users/john/projects")
),
Some("..\\..".to_string())
);
// Cross-branch paths
assert_eq!(
to_relative_path(
false,
Path::new("/Users/john/documents/file.txt"),
Path::new("/Users/john/projects")
),
Some("..\\documents\\file.txt".to_string())
);
// Root to subdirectory
assert_eq!(
to_relative_path(false, Path::new("/var/log/system.log"), Path::new("/")),
Some("var\\log\\system.log".to_string())
);
// Handles paths that would have leading slashes correctly
assert_eq!(
to_relative_path(false, Path::new("/home/user/file.txt"), Path::new("/home")),
Some("user\\file.txt".to_string())
);
// Test with current directory references that should be cleaned
assert_eq!(
to_relative_path(
false,
Path::new("/Users/john/projects/./app/src/main.rs"),
Path::new("/Users/john/projects")
),
Some("app\\src\\main.rs".to_string())
);
// Windows paths - different drives should return None
assert_eq!(
to_relative_path(
/* is_wsl */ false,
Path::new("D:\\projects\\app"),
Path::new("C:\\workspace")
),
None,
);
// Windows paths - same drive
assert_eq!(
to_relative_path(
/* is_wsl */ false,
Path::new("C:\\projects\\app\\src\\main.rs"),
Path::new("C:\\projects")
),
Some("app\\src\\main.rs".to_string())
);
// Windows paths - same drive -- WSL is disabled for now
assert_eq!(
to_relative_path(
/* is_wsl */ true,
Path::new("C:\\projects\\app\\src\\main.rs"),
Path::new("C:\\projects")
),
None
);
}
}
#[test]
fn test_normalize_relative_path_for_glob() {
use std::path::Path;
assert_eq!(
normalize_relative_path_for_glob(Path::new("app/src/main.rs")),
"app/src/main.rs"
);
assert_eq!(
normalize_relative_path_for_glob(Path::new("./app/src/main.rs")),
"app/src/main.rs"
);
assert_eq!(
normalize_relative_path_for_glob(Path::new("../app/src/main.rs")),
"app/src/main.rs"
);
assert_eq!(normalize_relative_path_for_glob(Path::new("..")), "");
assert_eq!(normalize_relative_path_for_glob(Path::new("")), "");
}
#[test]
fn test_posix_escape() {
let shell_family = ShellFamily::Posix;
assert_eq!(
shell_family.escape("~/test_dir/library% 1$2"),
"\\~/test_dir/library%\\ 1\\$2"
);
assert_eq!(shell_family.escape("あい"), "あい");
assert_eq!(shell_family.escape("abc \n \t"), "abc\\ \\\n\\ \\\t");
assert_eq!(shell_family.escape(""), "''");
assert_eq!(
shell_family.escape("foo '\"' bar"),
"foo\\ \\'\\\"\\'\\ bar"
);
}
#[test]
fn test_powershell_escape() {
let shell_family = ShellFamily::PowerShell;
assert_eq!(
shell_family.escape("~/test_dir/library% 1$2"),
"~/test_dir/library%` 1`$2"
);
assert_eq!(shell_family.escape("あい"), "あい");
assert_eq!(shell_family.escape("abc \n \t"), "abc` `\n` `\t");
assert_eq!(shell_family.escape(""), "''");
assert_eq!(shell_family.escape("foo '\"' bar"), "foo` `'`\"`'` bar");
}
#[test]
fn test_posix_unescape() {
let shell_family = ShellFamily::Posix;
// Escaped spaces
assert_eq!(shell_family.unescape("my\\ file.txt"), "my file.txt");
// Multiple escaped characters
assert_eq!(
shell_family.unescape("path/to/my\\ file\\ \\(1\\).txt"),
"path/to/my file (1).txt"
);
// No escaping needed — returns borrowed
assert!(matches!(
shell_family.unescape("simple.txt"),
std::borrow::Cow::Borrowed(_)
));
// Trailing backslash kept as-is
assert_eq!(shell_family.unescape("trailing\\"), "trailing\\");
// Roundtrip: unescape(escape(x)) == x
let original = "hello world $HOME 'quotes'";
assert_eq!(
shell_family.unescape(&shell_family.escape(original)),
original
);
}
#[test]
fn test_powershell_unescape() {
let shell_family = ShellFamily::PowerShell;
// Escaped spaces
assert_eq!(shell_family.unescape("my` file.txt"), "my file.txt");
// Multiple escaped characters
assert_eq!(shell_family.unescape("path` `$var"), "path $var");
// No escaping needed — returns borrowed
assert!(matches!(
shell_family.unescape("simple.txt"),
std::borrow::Cow::Borrowed(_)
));
// Roundtrip: unescape(escape(x)) == x
let original = "hello world $HOME";
assert_eq!(
shell_family.unescape(&shell_family.escape(original)),
original
);
}
#[test]
fn test_clean_path() {
assert_eq!(
CleanPathResult::with_line_and_column_number("Cargo.toml:10:5"),
CleanPathResult {
path: "Cargo.toml".into(),
line_and_column_num: Some(LineAndColumnArg {
line_num: 10,
column_num: Some(5)
}),
}
);
assert_eq!(
CleanPathResult::with_line_and_column_number("Cargo.toml:30:5abc"),
CleanPathResult {
path: "Cargo.toml:30:5abc".into(),
line_and_column_num: None
}
);
assert_eq!(
CleanPathResult::with_line_and_column_number("Cargo.toml[30,5]"),
CleanPathResult {
path: "Cargo.toml".into(),
line_and_column_num: Some(LineAndColumnArg {
line_num: 30,
column_num: Some(5)
})
}
);
assert_eq!(
CleanPathResult::with_line_and_column_number("Cargo.toml(3,1)"),
CleanPathResult {
path: "Cargo.toml".into(),
line_and_column_num: Some(LineAndColumnArg {
line_num: 3,
column_num: Some(1)
})
}
);
assert_eq!(
CleanPathResult::with_line_and_column_number("Cargo.toml\", line 100, in"),
CleanPathResult {
path: "Cargo.toml".into(),
line_and_column_num: Some(LineAndColumnArg {
line_num: 100,
column_num: None,
})
}
);
assert_eq!(
CleanPathResult::with_line_and_column_number("Cargo.toml\", line 5, column 20"),
CleanPathResult {
path: "Cargo.toml".into(),
line_and_column_num: Some(LineAndColumnArg {
line_num: 5,
column_num: Some(20),
})
}
);
assert_eq!(
CleanPathResult::with_line_and_column_number("Cargo.toml#L100"),
CleanPathResult {
path: "Cargo.toml".into(),
line_and_column_num: Some(LineAndColumnArg {
line_num: 100,
column_num: None
})
}
);
assert_eq!(
CleanPathResult::with_line_and_column_number("Cargo.toml#L100:4"),
CleanPathResult {
path: "Cargo.toml".into(),
line_and_column_num: Some(LineAndColumnArg {
line_num: 100,
column_num: Some(4)
})
}
);
// Line range format :start-end (should link to start line, ignore end line)
assert_eq!(
CleanPathResult::with_line_and_column_number("Cargo.toml:10-50"),
CleanPathResult {
path: "Cargo.toml".into(),
line_and_column_num: Some(LineAndColumnArg {
line_num: 10,
column_num: None
})
}
);
assert_eq!(
CleanPathResult::with_line_and_column_number("/path/to/file.rs:1-1000"),
CleanPathResult {
path: "/path/to/file.rs".into(),
line_and_column_num: Some(LineAndColumnArg {
line_num: 1,
column_num: None
})
}
);
assert_eq!(
CleanPathResult::with_line_and_column_number("src/main.rs:100-100"),
CleanPathResult {
path: "src/main.rs".into(),
line_and_column_num: Some(LineAndColumnArg {
line_num: 100,
column_num: None
})
}
);
}
#[test]
#[cfg(windows)]
fn test_msys2_exe_to_root() {
assert_eq!(
msys2_exe_to_root(WindowsPath::new(r"D:\Program Files\Git\usr\bin\git.exe")),
WindowsPathBuf::from(r"D:\Program Files\Git")
);
assert_eq!(
msys2_exe_to_root(WindowsPath::new(r"C:\git.exe")),
WindowsPathBuf::from(r"C:\Program Files\Git")
);
assert_eq!(
msys2_exe_to_root(WindowsPath::new(r"C:\foo\bar\baz\git.exe")),
WindowsPathBuf::from(r"C:\Program Files\Git")
);
assert_eq!(
msys2_exe_to_root(WindowsPath::new(r"C:\msys64\usr\bin\fish.exe")),
WindowsPathBuf::from(r"C:\msys64")
);
}
/// These tests all fail when running on UNIX.
#[test]
#[cfg(windows)]
fn test_convert_git_bash_to_windows_native_path() {
use std::sync::LazyLock;
static GIT_BASH_ROOT: LazyLock<WindowsPathBuf> =
LazyLock::new(|| WindowsPathBuf::from(r"C:\Program Files\Git"));
assert_eq!(
convert_msys2_to_windows_native_path(
&TypedPathBuf::from_unix("/c/foo/bar").to_path(),
&GIT_BASH_ROOT
)
.unwrap(),
PathBuf::from(r"C:\foo\bar")
);
assert_eq!(
convert_msys2_to_windows_native_path(
&TypedPathBuf::from_unix("/d/special folder").to_path(),
&GIT_BASH_ROOT
)
.unwrap(),
PathBuf::from(r"D:\special folder")
);
assert_eq!(
convert_msys2_to_windows_native_path(
&TypedPathBuf::from_unix("/z").to_path(),
&GIT_BASH_ROOT
)
.unwrap(),
PathBuf::from(r"Z:\")
);
// non-ascii isn't actually a valid drive name
assert!(matches!(
convert_msys2_to_windows_native_path(
&TypedPathBuf::from_unix("/😊/invalid").to_path(),
&GIT_BASH_ROOT
),
Err(MSYS2PathConversionError::NotInDrive)
));
assert!(matches!(
convert_msys2_to_windows_native_path(
&TypedPathBuf::from_unix("/aa/invalid").to_path(),
&GIT_BASH_ROOT
),
Err(MSYS2PathConversionError::NotInDrive)
));
assert_eq!(
convert_msys2_to_windows_native_path(
&TypedPathBuf::from_unix("//wsl$/Ubuntu/home").to_path(),
&GIT_BASH_ROOT
)
.unwrap(),
PathBuf::from(r"\\wsl$\Ubuntu\home")
);
assert_eq!(
convert_msys2_to_windows_native_path(
&TypedPathBuf::from_unix("//WSL.localhost/Ubuntu/home").to_path(),
&GIT_BASH_ROOT
)
.unwrap(),
PathBuf::from(r"\\WSL.localhost\Ubuntu\home")
);
// This path might get auto-inferred by typed-path to be a Windows path, even if it looks like
// UNIX with forward slashes.
assert_eq!(
convert_msys2_to_windows_native_path(
&TypedPath::from("//wsl$/Ubuntu/home"),
&GIT_BASH_ROOT
)
.unwrap(),
PathBuf::from(r"\\wsl$\Ubuntu\home")
);
assert_eq!(
convert_msys2_to_windows_native_path(
&TypedPathBuf::from_unix("~/.bash_history").to_path(),
&GIT_BASH_ROOT
)
.unwrap(),
PathBuf::from(r"~\.bash_history")
);
// Relative paths cannot be converted.
assert!(matches!(
convert_msys2_to_windows_native_path(
&TypedPathBuf::from_unix("some/relative/path").to_path(),
&GIT_BASH_ROOT
),
Err(MSYS2PathConversionError::PathNotAbsolute)
));
assert!(matches!(
convert_msys2_to_windows_native_path(
&TypedPathBuf::from_windows(r"C:\Users").to_path(),
&GIT_BASH_ROOT
),
Err(MSYS2PathConversionError::NonUnixPath)
));
assert_eq!(
convert_msys2_to_windows_native_path(
&TypedPathBuf::from_unix("/").to_path(),
&GIT_BASH_ROOT
)
.unwrap(),
PathBuf::from(r"C:\Program Files\Git")
);
assert_eq!(
convert_msys2_to_windows_native_path(
&TypedPathBuf::from_unix("/usr/bin").to_path(),
&GIT_BASH_ROOT
)
.unwrap(),
PathBuf::from(r"C:\Program Files\Git\usr\bin")
);
}
/// These tests all fail when running on UNIX.
#[test]
#[cfg(windows)]
fn test_convert_wsl_to_windows_host_path() {
assert_eq!(
convert_wsl_to_windows_host_path(
&TypedPathBuf::from_unix("/mnt/c/foo/bar").to_path(),
"Ubuntu"
)
.unwrap(),
PathBuf::from(r"C:\foo\bar")
);
assert_eq!(
convert_wsl_to_windows_host_path(
&TypedPathBuf::from_unix("/mnt/e/special dir").to_path(),
"Ubuntu"
)
.unwrap(),
PathBuf::from(r"E:\special dir")
);
assert_eq!(
convert_wsl_to_windows_host_path(&TypedPathBuf::from_unix("/mnt/z").to_path(), "Ubuntu")
.unwrap(),
PathBuf::from(r"Z:\")
);
assert_eq!(
convert_wsl_to_windows_host_path(
&TypedPathBuf::from_unix("/home/andy").to_path(),
"Ubuntu"
)
.unwrap(),
PathBuf::from(r"\\WSL$\Ubuntu\home\andy")
);
assert!(matches!(
convert_wsl_to_windows_host_path(
&TypedPathBuf::from_unix("some/relative/path").to_path(),
"Ubuntu"
),
Err(WSLPathConversionError::PathNotAbsolute)
));
assert!(matches!(
convert_wsl_to_windows_host_path(
&TypedPathBuf::from_unix("~/.bash_history").to_path(),
"Ubuntu"
),
Err(WSLPathConversionError::PathNotAbsolute)
));
// Two letters isn't actually a valid drive.
assert_eq!(
convert_wsl_to_windows_host_path(
&TypedPathBuf::from_unix("/mnt/aa/invalid_drive").to_path(),
"Ubuntu"
)
.unwrap(),
PathBuf::from(r"\\WSL$\Ubuntu\mnt\aa\invalid_drive")
);
assert_eq!(
convert_wsl_to_windows_host_path(
&TypedPathBuf::from_unix("/mnt/😊/invalid_drive").to_path(),
"Ubuntu"
)
.unwrap(),
PathBuf::from(r"\\WSL$\Ubuntu\mnt\😊\invalid_drive")
);
assert!(matches!(
convert_wsl_to_windows_host_path(
&TypedPathBuf::from_windows(r"C:\Users").to_path(),
"Ubuntu"
),
Err(WSLPathConversionError::NonUnixPath)
));
}
#[test]
fn test_convert_windows_path_to_wsl() {
assert_eq!(
convert_windows_path_to_wsl(r"C:\Users\aloke\file.txt"),
"/mnt/c/Users/aloke/file.txt"
);
assert_eq!(
convert_windows_path_to_wsl(r"D:\Pictures\Screenshots\Screenshot 2025-05-14 155816.png"),
"/mnt/d/Pictures/Screenshots/Screenshot 2025-05-14 155816.png"
);
// Drive letter only
assert_eq!(convert_windows_path_to_wsl(r"C:\"), "/mnt/c");
assert_eq!(convert_windows_path_to_wsl("C:"), "/mnt/c");
// Uppercase drive letter should be lowercased
assert_eq!(convert_windows_path_to_wsl(r"E:\foo"), "/mnt/e/foo");
// Non-drive path (e.g. UNC) gets backslashes replaced
assert_eq!(
convert_windows_path_to_wsl(r"\\server\share\file"),
"//server/share/file"
);
}
#[test]
fn test_convert_windows_path_to_msys2() {
assert_eq!(
convert_windows_path_to_msys2(r"C:\Users\aloke\file.txt"),
"/c/Users/aloke/file.txt"
);
assert_eq!(
convert_windows_path_to_msys2(r"D:\Pictures\Screenshots\Screenshot 2025-05-14 155816.png"),
"/d/Pictures/Screenshots/Screenshot 2025-05-14 155816.png"
);
// Drive letter only
assert_eq!(convert_windows_path_to_msys2(r"C:\"), "/c");
assert_eq!(convert_windows_path_to_msys2("C:"), "/c");
// Uppercase drive letter should be lowercased
assert_eq!(convert_windows_path_to_msys2(r"E:\foo"), "/e/foo");
// Non-drive path (e.g. UNC) gets backslashes replaced
assert_eq!(
convert_windows_path_to_msys2(r"\\server\share\file"),
"//server/share/file"
);
}
#[test]
fn test_canonicalize_git_bash_path() {
assert_eq!(
canonicalize_git_bash_path(
Path::new("C:")
.join("Program Files")
.join("Git")
.join("bin")
.join("bash.exe")
),
Path::new("C:")
.join("Program Files")
.join("Git")
.join("usr")
.join("bin")
.join("bash.exe")
);
assert_eq!(
canonicalize_git_bash_path(
Path::new("C:")
.join("Windows")
.join("system32")
.join("bash.exe")
),
Path::new("C:")
.join("Windows")
.join("system32")
.join("bash.exe")
);
}
// ── group_roots_by_common_ancestor tests ─────────────────────────────
mod group_roots_by_common_ancestor_tests {
use crate::path::group_roots_by_common_ancestor;
use std::path::PathBuf;
fn pb(s: &str) -> PathBuf {
PathBuf::from(s)
}
#[test]
fn empty_input_produces_empty_grouping() {
let grouping = group_roots_by_common_ancestor::<PathBuf>(&[]);
assert!(grouping.roots.is_empty());
assert!(grouping.absorbed_by_root.is_empty());
}
#[test]
fn single_path_survives_with_no_absorbed() {
let grouping = group_roots_by_common_ancestor(&[pb("/a")]);
assert_eq!(grouping.roots, vec![pb("/a")]);
assert!(grouping.absorbed_by_root.is_empty());
}
#[test]
fn unrelated_siblings_both_survive() {
let grouping = group_roots_by_common_ancestor(&[pb("/a"), pb("/b")]);
assert_eq!(grouping.roots, vec![pb("/a"), pb("/b")]);
assert!(grouping.absorbed_by_root.is_empty());
}
#[test]
fn descendant_absorbed_into_ancestor() {
// Ancestor listed first.
let grouping = group_roots_by_common_ancestor(&[pb("/a"), pb("/a/b")]);
assert_eq!(grouping.roots, vec![pb("/a")]);
assert_eq!(grouping.absorbed_by_root.len(), 1);
assert_eq!(grouping.absorbed_by_root[&pb("/a")], vec![pb("/a/b")]);
}
#[test]
fn descendant_first_still_absorbed() {
// Descendant listed first, ancestor second; survivor is still the
// ancestor and its input order is preserved.
let grouping = group_roots_by_common_ancestor(&[pb("/a/b"), pb("/a")]);
assert_eq!(grouping.roots, vec![pb("/a")]);
assert_eq!(grouping.absorbed_by_root[&pb("/a")], vec![pb("/a/b")]);
}
#[test]
fn three_deep_chain_collapses_to_root() {
// Descendant order in input is preserved in the absorbed list.
let grouping = group_roots_by_common_ancestor(&[pb("/a/b/c"), pb("/a/b"), pb("/a")]);
assert_eq!(grouping.roots, vec![pb("/a")]);
assert_eq!(
grouping.absorbed_by_root[&pb("/a")],
vec![pb("/a/b/c"), pb("/a/b")]
);
}
#[test]
fn mixed_groups_absorb_independently() {
let grouping =
group_roots_by_common_ancestor(&[pb("/a"), pb("/x"), pb("/a/b"), pb("/x/y")]);
assert_eq!(grouping.roots, vec![pb("/a"), pb("/x")]);
assert_eq!(grouping.absorbed_by_root[&pb("/a")], vec![pb("/a/b")]);
assert_eq!(grouping.absorbed_by_root[&pb("/x")], vec![pb("/x/y")]);
}
#[test]
fn same_prefix_different_component_name_both_survive() {
// /foo/a is NOT an ancestor of /foo/abc (component-aware match).
let grouping = group_roots_by_common_ancestor(&[pb("/foo/a"), pb("/foo/abc")]);
assert_eq!(grouping.roots, vec![pb("/foo/a"), pb("/foo/abc")]);
assert!(grouping.absorbed_by_root.is_empty());
}
#[test]
fn duplicate_inputs_collapse_to_single_survivor() {
let grouping = group_roots_by_common_ancestor(&[pb("/a"), pb("/a")]);
assert_eq!(grouping.roots, vec![pb("/a")]);
assert!(grouping.absorbed_by_root.is_empty());
}
#[test]
fn surviving_root_order_matches_input_order() {
// Insert a descendant between two surviving ancestors; the survivors
// should appear in their original input order even though processing
// sorted by component count.
let grouping = group_roots_by_common_ancestor(&[pb("/b"), pb("/a/x"), pb("/a"), pb("/c")]);
assert_eq!(grouping.roots, vec![pb("/b"), pb("/a"), pb("/c")]);
assert_eq!(grouping.absorbed_by_root[&pb("/a")], vec![pb("/a/x")]);
}
#[test]
fn descendant_absorbed_by_closest_ancestor_not_furthest() {
// Both /a and /a/b are surviving ancestors of /a/b/c... wait, /a/b is
// itself absorbed into /a. So /a/b/c should be absorbed into /a as
// well (the only surviving ancestor).
let grouping = group_roots_by_common_ancestor(&[pb("/a"), pb("/a/b"), pb("/a/b/c")]);
assert_eq!(grouping.roots, vec![pb("/a")]);
assert_eq!(
grouping.absorbed_by_root[&pb("/a")],
vec![pb("/a/b"), pb("/a/b/c")]
);
}
}