use normalize_path::NormalizePath;
use std::{
fmt,
path::{Path, PathBuf},
};
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
pub(crate) struct VfsPath(VfsPathRepr);
impl VfsPath {
#[expect(dead_code, reason = "We do not use virtual paths yet")]
pub(crate) fn new_virtual_path(path: String) -> Self {
assert!(path.starts_with('/'));
Self(VfsPathRepr::VirtualPath(VirtualPath(path)))
}
#[expect(
dead_code,
reason = "VFS path operations are scaffolded for workspace import handling"
)]
pub(crate) fn new_real_path(path: String) -> Self {
let p = PathBuf::from(path);
if !p.is_absolute() {
panic!("expected an absolute path, got {}", p.to_string_lossy())
}
Self::from(p)
}
pub(crate) fn as_path(&self) -> Option<&Path> {
match &self.0 {
VfsPathRepr::PathBuf(it) => Some(it.as_path()),
VfsPathRepr::VirtualPath(_) => None,
}
}
#[expect(
dead_code,
reason = "VFS path operations are scaffolded for workspace import handling"
)]
pub(crate) fn into_abs_path(self) -> Option<PathBuf> {
match self.0 {
VfsPathRepr::PathBuf(it) => Some(it),
VfsPathRepr::VirtualPath(_) => None,
}
}
#[expect(
dead_code,
reason = "VFS path operations are scaffolded for workspace import handling"
)]
pub(crate) fn join(&self, path: &str) -> Option<Self> {
match &self.0 {
VfsPathRepr::PathBuf(it) => {
let res = it.join(path).normalize();
Some(Self(VfsPathRepr::PathBuf(res)))
}
VfsPathRepr::VirtualPath(it) => {
let res = it.join(path)?;
Some(Self(VfsPathRepr::VirtualPath(res)))
}
}
}
pub(crate) fn pop(&mut self) -> bool {
match &mut self.0 {
VfsPathRepr::PathBuf(it) => it.pop(),
VfsPathRepr::VirtualPath(it) => it.pop(),
}
}
#[expect(
dead_code,
reason = "VFS path operations are scaffolded for workspace import handling"
)]
pub(crate) fn starts_with(&self, other: &Self) -> bool {
match (&self.0, &other.0) {
(VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.starts_with(rhs),
(VfsPathRepr::VirtualPath(lhs), VfsPathRepr::VirtualPath(rhs)) => lhs.starts_with(rhs),
(VfsPathRepr::PathBuf(_) | VfsPathRepr::VirtualPath(_), _) => false,
}
}
#[expect(
dead_code,
reason = "VFS path operations are scaffolded for workspace import handling"
)]
pub(crate) fn parent(&self) -> Option<Self> {
let mut parent = self.clone();
if parent.pop() { Some(parent) } else { None }
}
}
#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
enum VfsPathRepr {
PathBuf(PathBuf),
VirtualPath(VirtualPath),
}
impl From<PathBuf> for VfsPath {
fn from(v: PathBuf) -> Self {
Self(VfsPathRepr::PathBuf(v.normalize()))
}
}
impl fmt::Display for VfsPath {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match &self.0 {
VfsPathRepr::PathBuf(it) => it.to_string_lossy().fmt(f),
VfsPathRepr::VirtualPath(VirtualPath(it)) => it.fmt(f),
}
}
}
impl fmt::Debug for VfsPath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
fmt::Debug::fmt(&self.0, f)
}
}
impl fmt::Debug for VfsPathRepr {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::PathBuf(it) => it.fmt(f),
Self::VirtualPath(VirtualPath(it)) => it.fmt(f),
}
}
}
impl PartialEq<Path> for VfsPath {
fn eq(&self, other: &Path) -> bool {
match &self.0 {
VfsPathRepr::PathBuf(lhs) => lhs == other,
VfsPathRepr::VirtualPath(_) => false,
}
}
}
impl PartialEq<VfsPath> for Path {
fn eq(&self, other: &VfsPath) -> bool {
other == self
}
}
#[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
struct VirtualPath(String);
impl VirtualPath {
fn starts_with(&self, other: &Self) -> bool {
self.0.starts_with(&other.0)
}
fn pop(&mut self) -> bool {
let pos = match self.0.rfind('/') {
Some(pos) => pos,
None => return false,
};
self.0 = self.0[..pos].to_string();
true
}
fn join(&self, mut path: &str) -> Option<Self> {
let mut res = self.clone();
while path.starts_with("../") {
if !res.pop() {
return None;
}
path = &path["../".len()..];
}
path = path.trim_start_matches("./");
res.0 = format!("{}/{path}", res.0);
Some(res)
}
}