Skip to content

Commit 95d1254

Browse files
committed
Restore FileSystem
1 parent dfe3477 commit 95d1254

File tree

8 files changed

+873
-152
lines changed

8 files changed

+873
-152
lines changed

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/ruff_db/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,5 @@ countme = { workspace = true }
1616
dashmap = { workspace = true }
1717
filetime = { workspace = true }
1818
salsa = { workspace = true }
19+
tracing = { workspace = true }
1920
rustc-hash = { workspace = true }

crates/ruff_db/src/file_system.rs

Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
use std::fmt::Formatter;
2+
use std::ops::Deref;
3+
use std::path::Path;
4+
5+
use camino::{Utf8Path, Utf8PathBuf};
6+
use filetime::FileTime;
7+
8+
pub use memory::MemoryFileSystem;
9+
pub use os::OsFileSystem;
10+
11+
mod memory;
12+
mod os;
13+
14+
pub type Result<T> = std::io::Result<T>;
15+
16+
/// A file system that can be used to read and write files.
17+
///
18+
/// The file system is agnostic to the actual storage medium, it could be a real file system, a combination
19+
/// of a real file system and an in-memory file system in the case of an LSP where unsaved changes are stored in memory,
20+
/// or an all in-memory file system for testing.
21+
pub trait FileSystem {
22+
/// Reads the metadata of the file or directory at `path`.
23+
fn metadata(&self, path: &FileSystemPath) -> Result<Metadata>;
24+
25+
/// Reads the content of the file at `path`.
26+
fn read(&self, path: &FileSystemPath) -> Result<String>;
27+
28+
/// Returns `true` if `path` exists.
29+
fn exists(&self, path: &FileSystemPath) -> bool;
30+
}
31+
32+
// TODO support untitled files for the LSP use case. Wrap a `str` and `String`
33+
// The main question is how `as_std_path` would work for untitled files, that can only exist in the LSP case
34+
// but there's no compile time guarantee that a [`OsFileSystem`] never gets an untitled file path.
35+
36+
/// Path to a file or directory stored in [`FileSystem`].
37+
///
38+
/// The path is guaranteed to be valid UTF-8.
39+
#[repr(transparent)]
40+
#[derive(Eq, PartialEq, Hash)]
41+
pub struct FileSystemPath(Utf8Path);
42+
43+
impl FileSystemPath {
44+
pub fn new(path: &(impl AsRef<Utf8Path> + ?Sized)) -> &Self {
45+
let path = path.as_ref();
46+
unsafe { &*(path as *const Utf8Path as *const FileSystemPath) }
47+
}
48+
49+
/// Converts the path to an owned [`FileSystemPathBuf`].
50+
pub fn to_path_buf(&self) -> FileSystemPathBuf {
51+
FileSystemPathBuf(self.0.to_path_buf())
52+
}
53+
54+
/// Returns the path as a string slice.
55+
pub fn as_str(&self) -> &str {
56+
self.0.as_str()
57+
}
58+
59+
/// Returns the std path for the file.
60+
pub fn as_std_path(&self) -> &Path {
61+
self.0.as_std_path()
62+
}
63+
}
64+
65+
/// Owned path to a file or directory stored in [`FileSystem`].
66+
///
67+
/// The path is guaranteed to be valid UTF-8.
68+
#[repr(transparent)]
69+
#[derive(Eq, PartialEq, Clone, Hash)]
70+
pub struct FileSystemPathBuf(Utf8PathBuf);
71+
72+
impl Default for FileSystemPathBuf {
73+
fn default() -> Self {
74+
Self::new()
75+
}
76+
}
77+
78+
impl FileSystemPathBuf {
79+
pub fn new() -> Self {
80+
Self(Utf8PathBuf::new())
81+
}
82+
83+
pub fn as_path(&self) -> &FileSystemPath {
84+
// SAFETY: FsPath is marked as #[repr(transparent)] so the conversion from a
85+
// *const Utf8Path to a *const FsPath is valid.
86+
unsafe { &*(self.0.as_path() as *const Utf8Path as *const FileSystemPath) }
87+
}
88+
}
89+
90+
impl AsRef<FileSystemPath> for FileSystemPathBuf {
91+
fn as_ref(&self) -> &FileSystemPath {
92+
self.as_path()
93+
}
94+
}
95+
96+
impl AsRef<FileSystemPath> for FileSystemPath {
97+
#[inline]
98+
fn as_ref(&self) -> &FileSystemPath {
99+
self
100+
}
101+
}
102+
103+
impl AsRef<FileSystemPath> for str {
104+
#[inline]
105+
fn as_ref(&self) -> &FileSystemPath {
106+
FileSystemPath::new(self)
107+
}
108+
}
109+
110+
impl AsRef<FileSystemPath> for String {
111+
#[inline]
112+
fn as_ref(&self) -> &FileSystemPath {
113+
FileSystemPath::new(self)
114+
}
115+
}
116+
117+
impl AsRef<Path> for FileSystemPath {
118+
#[inline]
119+
fn as_ref(&self) -> &Path {
120+
self.0.as_std_path()
121+
}
122+
}
123+
124+
impl Deref for FileSystemPathBuf {
125+
type Target = FileSystemPath;
126+
127+
fn deref(&self) -> &Self::Target {
128+
self.as_path()
129+
}
130+
}
131+
132+
impl std::fmt::Debug for FileSystemPath {
133+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
134+
self.0.fmt(f)
135+
}
136+
}
137+
138+
impl std::fmt::Display for FileSystemPath {
139+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
140+
self.0.fmt(f)
141+
}
142+
}
143+
144+
impl std::fmt::Debug for FileSystemPathBuf {
145+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
146+
self.0.fmt(f)
147+
}
148+
}
149+
150+
impl std::fmt::Display for FileSystemPathBuf {
151+
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
152+
self.0.fmt(f)
153+
}
154+
}
155+
156+
#[derive(Clone, Debug, Eq, PartialEq)]
157+
pub struct Metadata {
158+
revision: FileRevision,
159+
permissions: Option<u32>,
160+
file_type: FileType,
161+
}
162+
163+
impl Metadata {
164+
pub fn revision(&self) -> FileRevision {
165+
self.revision
166+
}
167+
168+
pub fn permissions(&self) -> Option<u32> {
169+
self.permissions
170+
}
171+
172+
pub fn file_type(&self) -> FileType {
173+
self.file_type
174+
}
175+
}
176+
177+
/// A number representing the revision of a file.
178+
///
179+
/// Two revisions that don't compare equal signify that the file has been modified.
180+
/// Revisions aren't guaranteed to be monotonically increasing or in any specific order.
181+
///
182+
/// Possible revisions are:
183+
/// * The last modification time of the file.
184+
/// * The hash of the file's content.
185+
/// * The revision as it comes from an external system, for example the LSP.
186+
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
187+
pub struct FileRevision(u128);
188+
189+
impl FileRevision {
190+
pub fn new(value: u128) -> Self {
191+
Self(value)
192+
}
193+
194+
pub const fn zero() -> Self {
195+
Self(0)
196+
}
197+
198+
#[must_use]
199+
pub fn as_u128(self) -> u128 {
200+
self.0
201+
}
202+
}
203+
204+
impl From<u128> for FileRevision {
205+
fn from(value: u128) -> Self {
206+
FileRevision(value)
207+
}
208+
}
209+
210+
impl From<u64> for FileRevision {
211+
fn from(value: u64) -> Self {
212+
FileRevision(u128::from(value))
213+
}
214+
}
215+
216+
impl From<FileTime> for FileRevision {
217+
fn from(value: FileTime) -> Self {
218+
let seconds = value.seconds() as u128;
219+
let seconds = seconds << 64;
220+
let nanos = value.nanoseconds() as u128;
221+
222+
FileRevision(seconds | nanos)
223+
}
224+
}
225+
226+
#[derive(Copy, Clone, Eq, PartialEq, Debug, Hash)]
227+
pub enum FileType {
228+
File,
229+
Directory,
230+
Symlink,
231+
}
232+
233+
impl FileType {
234+
pub const fn is_file(self) -> bool {
235+
matches!(self, FileType::File)
236+
}
237+
238+
pub const fn is_directory(self) -> bool {
239+
matches!(self, FileType::Directory)
240+
}
241+
242+
pub const fn is_symlink(self) -> bool {
243+
matches!(self, FileType::Symlink)
244+
}
245+
}
246+
247+
#[cfg(test)]
248+
mod tests {
249+
use crate::file_system::FileRevision;
250+
use filetime::FileTime;
251+
252+
#[test]
253+
fn revision_from_file_time() {
254+
let file_time = FileTime::now();
255+
let revision = FileRevision::from(file_time);
256+
257+
let revision = revision.as_u128();
258+
259+
let nano = revision & 0xFFFF_FFFF_FFFF_FFFF;
260+
let seconds = revision >> 64;
261+
262+
assert_eq!(file_time.nanoseconds(), nano as u32);
263+
assert_eq!(file_time.seconds(), seconds as i64);
264+
}
265+
}

0 commit comments

Comments
 (0)