fix: resolve . and .. in terminal cd and prevent panic in path elide

`absolutize` now lexically normalizes paths so `cd ..` navigates to the
parent directory instead of appending `..` literally. Also guard against
`file_name()` returning `None` in `fmt_path_elide_ex`, which caused a
panic on paths containing unresolved `..` components.

Closes #402
This commit is contained in:
Christian Visintin
2026-03-21 10:44:26 +01:00
parent 2e826a560a
commit d4bf9cc4a6
2 changed files with 63 additions and 3 deletions

View File

@@ -67,8 +67,10 @@ pub fn fmt_path_elide_ex(p: &Path, width: usize, extra_len: usize) -> String {
// If ancestors_len is bigger than 3, push '…' and parent too
if ancestors_len > 3 {
elided_path.push("");
if let Some(parent) = p.ancestors().nth(1) {
elided_path.push(parent.file_name().unwrap());
if let Some(parent) = p.ancestors().nth(1)
&& let Some(name) = parent.file_name()
{
elided_path.push(name);
}
}
// Push file_name

View File

@@ -12,14 +12,34 @@ use std::path::{Component, Path, PathBuf};
/// assert_eq!(absolutize(&Path::new("/home/omar"), &Path::new("/tmp/readme.txt")).as_path(), Path::new("/tmp/readme.txt"));
/// ```
pub fn absolutize(wrkdir: &Path, target: &Path) -> PathBuf {
match target.is_absolute() {
let raw = match target.is_absolute() {
true => target.to_path_buf(),
false => {
let mut p: PathBuf = wrkdir.to_path_buf();
p.push(target);
p
}
};
normalize(&raw)
}
/// Normalize a path by resolving `.` and `..` components lexically
/// (without touching the filesystem).
fn normalize(path: &Path) -> PathBuf {
let mut parts: Vec<Component> = Vec::new();
for component in path.components() {
match component {
Component::ParentDir => {
// Pop the last normal component; never pop past root
if let Some(Component::Normal(_)) = parts.last() {
parts.pop();
}
}
Component::CurDir => {} // skip `.`
other => parts.push(other),
}
}
parts.iter().collect()
}
/// This function will get the difference from path `path` to `base`. Basically will remove `base` from `path`
@@ -99,6 +119,44 @@ mod test {
);
}
#[test]
fn absolutize_resolves_parent_dir() {
assert_eq!(
absolutize(Path::new("/home/omar"), Path::new("..")).as_path(),
Path::new("/home")
);
assert_eq!(
absolutize(Path::new("/home/omar"), Path::new("../docs")).as_path(),
Path::new("/home/docs")
);
assert_eq!(
absolutize(Path::new("/home"), Path::new("..")).as_path(),
Path::new("/")
);
}
#[test]
fn absolutize_resolves_current_dir() {
assert_eq!(
absolutize(Path::new("/home/omar"), Path::new(".")).as_path(),
Path::new("/home/omar")
);
assert_eq!(
absolutize(Path::new("/home/omar"), Path::new("./docs")).as_path(),
Path::new("/home/omar/docs")
);
}
#[test]
fn normalize_path() {
assert_eq!(normalize(Path::new("/a/b/../c")), Path::new("/a/c"));
assert_eq!(normalize(Path::new("/a/b/./c")), Path::new("/a/b/c"));
assert_eq!(normalize(Path::new("/a/b/c/../..")), Path::new("/a"));
assert_eq!(normalize(Path::new("/")), Path::new("/"));
// Parent beyond root stays at root
assert_eq!(normalize(Path::new("/..")), Path::new("/"));
}
#[test]
fn calc_diff_paths() {
assert_eq!(