Skip to main content

yazi_shared/loc/
loc.rs

1use std::{hash::{Hash, Hasher}, marker::PhantomData, ops::Deref};
2
3use anyhow::{Result, bail};
4
5use super::LocAbleImpl;
6use crate::{loc::{LocAble, LocBuf, LocBufAble, StrandAbleImpl}, path::{AsPath, AsPathView, PathDyn}, scheme::SchemeKind, strand::AsStrandView};
7
8#[derive(Clone, Copy, Debug)]
9pub struct Loc<'p, P = &'p std::path::Path> {
10	pub(super) inner:    P,
11	pub(super) uri:      usize,
12	pub(super) urn:      usize,
13	pub(super) _phantom: PhantomData<&'p ()>,
14}
15
16impl<'p, P> Default for Loc<'p, P>
17where
18	P: LocAble<'p> + LocAbleImpl<'p>,
19{
20	fn default() -> Self { Self { inner: P::empty(), uri: 0, urn: 0, _phantom: PhantomData } }
21}
22
23impl<'p, P> Deref for Loc<'p, P>
24where
25	P: LocAble<'p>,
26{
27	type Target = P;
28
29	fn deref(&self) -> &Self::Target { &self.inner }
30}
31
32impl<'p, P> AsPath for Loc<'p, P>
33where
34	P: LocAble<'p> + AsPath,
35{
36	fn as_path(&self) -> PathDyn<'_> { self.inner.as_path() }
37}
38
39// FIXME: remove
40impl AsRef<std::path::Path> for Loc<'_, &std::path::Path> {
41	fn as_ref(&self) -> &std::path::Path { self.inner }
42}
43
44// --- Hash
45impl<'p, P> Hash for Loc<'p, P>
46where
47	P: LocAble<'p> + Hash,
48{
49	fn hash<H: Hasher>(&self, state: &mut H) { self.inner.hash(state) }
50}
51
52impl<'p, P> From<Loc<'p, P>> for LocBuf<<P as LocAble<'p>>::Owned>
53where
54	P: LocAble<'p> + LocAbleImpl<'p>,
55	<P as LocAble<'p>>::Owned: LocBufAble,
56{
57	fn from(value: Loc<'p, P>) -> Self {
58		Self { inner: value.inner.to_path_buf(), uri: value.uri, urn: value.urn }
59	}
60}
61
62// --- Eq
63impl<'p, P> PartialEq for Loc<'p, P>
64where
65	P: LocAble<'p> + PartialEq,
66{
67	fn eq(&self, other: &Self) -> bool { self.inner == other.inner }
68}
69
70impl<'p, P> Eq for Loc<'p, P> where P: LocAble<'p> + Eq {}
71
72impl<'p, P> Loc<'p, P>
73where
74	P: LocAble<'p> + LocAbleImpl<'p>,
75{
76	#[inline]
77	pub fn as_inner(self) -> P { self.inner }
78
79	#[inline]
80	pub fn as_loc(self) -> Self { self }
81
82	pub fn bare<T>(path: T) -> Self
83	where
84		T: AsPathView<'p, P>,
85	{
86		let path = path.as_path_view();
87		let Some(name) = path.file_name() else {
88			let p = path.strip_prefix(P::empty()).unwrap();
89			return Self { inner: p, uri: 0, urn: 0, _phantom: PhantomData };
90		};
91
92		let name_len = name.len();
93		let prefix_len = unsafe {
94			name.as_encoded_bytes().as_ptr().offset_from_unsigned(path.as_encoded_bytes().as_ptr())
95		};
96
97		let bytes = &path.as_encoded_bytes()[..prefix_len + name_len];
98		Self {
99			inner:    unsafe { P::from_encoded_bytes_unchecked(bytes) },
100			uri:      name_len,
101			urn:      name_len,
102			_phantom: PhantomData,
103		}
104	}
105
106	#[inline]
107	pub fn base(self) -> P {
108		unsafe {
109			P::from_encoded_bytes_unchecked(
110				self.inner.as_encoded_bytes().get_unchecked(..self.inner.len() - self.uri),
111			)
112		}
113	}
114
115	pub fn floated<'a, T, S>(path: T, base: S) -> Self
116	where
117		T: AsPathView<'p, P>,
118		S: AsStrandView<'a, P::Strand<'a>>,
119	{
120		let mut loc = Self::bare(path);
121		loc.uri = loc.inner.strip_prefix(base).expect("Loc must start with the given base").len();
122		loc
123	}
124
125	#[inline]
126	pub fn has_base(self) -> bool { self.inner.len() != self.uri }
127
128	#[inline]
129	pub fn has_trail(self) -> bool { self.inner.len() != self.urn }
130
131	#[inline]
132	pub fn is_empty(self) -> bool { self.inner.len() == 0 }
133
134	pub fn new<'a, T, S>(path: T, base: S, trail: S) -> Self
135	where
136		T: AsPathView<'p, P>,
137		S: AsStrandView<'a, P::Strand<'a>>,
138	{
139		let mut loc = Self::bare(path);
140		loc.uri = loc.inner.strip_prefix(base).expect("Loc must start with the given base").len();
141		loc.urn = loc.inner.strip_prefix(trail).expect("Loc must start with the given trail").len();
142		loc
143	}
144
145	#[inline]
146	pub fn parent(self) -> Option<P> {
147		self.inner.parent().filter(|p| !p.as_encoded_bytes().is_empty())
148	}
149
150	pub fn saturated<'a, T>(path: T, kind: SchemeKind) -> Self
151	where
152		T: AsPathView<'p, P>,
153	{
154		match kind {
155			SchemeKind::Regular => Self::bare(path),
156			SchemeKind::Search => Self::zeroed(path),
157			SchemeKind::Archive => Self::zeroed(path),
158			SchemeKind::Sftp => Self::bare(path),
159		}
160	}
161
162	#[inline]
163	pub fn trail(self) -> P {
164		unsafe {
165			P::from_encoded_bytes_unchecked(
166				self.inner.as_encoded_bytes().get_unchecked(..self.inner.len() - self.urn),
167			)
168		}
169	}
170
171	#[inline]
172	pub fn triple(self) -> (P, P, P) {
173		let len = self.inner.len();
174
175		let base = ..len - self.uri;
176		let rest = len - self.uri..len - self.urn;
177		let urn = len - self.urn..;
178
179		unsafe {
180			(
181				P::from_encoded_bytes_unchecked(self.inner.as_encoded_bytes().get_unchecked(base)),
182				P::from_encoded_bytes_unchecked(self.inner.as_encoded_bytes().get_unchecked(rest)),
183				P::from_encoded_bytes_unchecked(self.inner.as_encoded_bytes().get_unchecked(urn)),
184			)
185		}
186	}
187
188	#[inline]
189	pub fn uri(self) -> P {
190		unsafe {
191			P::from_encoded_bytes_unchecked(
192				self.inner.as_encoded_bytes().get_unchecked(self.inner.len() - self.uri..),
193			)
194		}
195	}
196
197	#[inline]
198	pub fn urn(self) -> P {
199		unsafe {
200			P::from_encoded_bytes_unchecked(
201				self.inner.as_encoded_bytes().get_unchecked(self.inner.len() - self.urn..),
202			)
203		}
204	}
205
206	pub fn with<T>(path: T, uri: usize, urn: usize) -> Result<Self>
207	where
208		T: AsPathView<'p, P>,
209	{
210		if urn > uri {
211			bail!("URN cannot be longer than URI");
212		}
213
214		let mut loc = Self::bare(path);
215		if uri == 0 {
216			(loc.uri, loc.urn) = (0, 0);
217			return Ok(loc);
218		} else if urn == 0 {
219			loc.urn = 0;
220		}
221
222		let mut it = loc.inner.components();
223		for i in 1..=uri {
224			if it.next_back().is_none() {
225				bail!("URI exceeds the entire URL");
226			}
227			if i == urn {
228				loc.urn = loc.strip_prefix(it.clone()).unwrap().len();
229			}
230			if i == uri {
231				loc.uri = loc.strip_prefix(it).unwrap().len();
232				break;
233			}
234		}
235		Ok(loc)
236	}
237
238	pub fn zeroed<T>(path: T) -> Self
239	where
240		T: AsPathView<'p, P>,
241	{
242		let mut loc = Self::bare(path);
243		(loc.uri, loc.urn) = (0, 0);
244		loc
245	}
246}
247
248#[cfg(test)]
249mod tests {
250	use super::*;
251
252	#[test]
253	fn test_with() -> Result<()> {
254		let cases = [
255			// Relative paths
256			("tmp/test.zip/foo/bar", 3, 2, "test.zip/foo/bar", "foo/bar"),
257			("tmp/test.zip/foo/bar/", 3, 2, "test.zip/foo/bar", "foo/bar"),
258			// Absolute paths
259			("/tmp/test.zip/foo/bar", 3, 2, "test.zip/foo/bar", "foo/bar"),
260			("/tmp/test.zip/foo/bar/", 3, 2, "test.zip/foo/bar", "foo/bar"),
261			// Relative path with parent components
262			("tmp/test.zip/foo/bar/../..", 5, 4, "test.zip/foo/bar/../..", "foo/bar/../.."),
263			("tmp/test.zip/foo/bar/../../", 5, 4, "test.zip/foo/bar/../..", "foo/bar/../.."),
264			// Absolute path with parent components
265			("/tmp/test.zip/foo/bar/../..", 5, 4, "test.zip/foo/bar/../..", "foo/bar/../.."),
266			("/tmp/test.zip/foo/bar/../../", 5, 4, "test.zip/foo/bar/../..", "foo/bar/../.."),
267		];
268
269		for (path, uri, urn, expect_uri, expect_urn) in cases {
270			let loc = Loc::with(std::path::Path::new(path), uri, urn)?;
271			assert_eq!(loc.uri().to_str().unwrap(), expect_uri);
272			assert_eq!(loc.urn().to_str().unwrap(), expect_urn);
273
274			let loc = Loc::with(typed_path::UnixPath::new(path), uri, urn)?;
275			assert_eq!(loc.uri().to_str().unwrap(), expect_uri);
276			assert_eq!(loc.urn().to_str().unwrap(), expect_urn);
277		}
278
279		Ok(())
280	}
281}