aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/vfs/src/anchored_path.rs10
-rw-r--r--crates/vfs/src/file_set.rs42
-rw-r--r--crates/vfs/src/lib.rs50
-rw-r--r--crates/vfs/src/loader.rs65
-rw-r--r--crates/vfs/src/vfs_path.rs39
5 files changed, 204 insertions, 2 deletions
diff --git a/crates/vfs/src/anchored_path.rs b/crates/vfs/src/anchored_path.rs
index 02720a32e..db15a2a21 100644
--- a/crates/vfs/src/anchored_path.rs
+++ b/crates/vfs/src/anchored_path.rs
@@ -26,14 +26,24 @@
26//! from the anchor than. 26//! from the anchor than.
27use crate::FileId; 27use crate::FileId;
28 28
29/// Path relative to a file.
30///
31/// Owned version of [`AnchoredPath`].
29#[derive(Clone, PartialEq, Eq, Debug)] 32#[derive(Clone, PartialEq, Eq, Debug)]
30pub struct AnchoredPathBuf { 33pub struct AnchoredPathBuf {
34 /// File that this path is relative to.
31 pub anchor: FileId, 35 pub anchor: FileId,
36 /// Path relative to `anchor`'s containing directory.
32 pub path: String, 37 pub path: String,
33} 38}
34 39
40/// Path relative to a file.
41///
42/// Borrowed version of [`AnchoredPathBuf`].
35#[derive(Clone, Copy, PartialEq, Eq, Debug)] 43#[derive(Clone, Copy, PartialEq, Eq, Debug)]
36pub struct AnchoredPath<'a> { 44pub struct AnchoredPath<'a> {
45 /// File that this path is relative to.
37 pub anchor: FileId, 46 pub anchor: FileId,
47 /// Path relative to `anchor`'s containing directory.
38 pub path: &'a str, 48 pub path: &'a str,
39} 49}
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs
index 49ca593ac..348b6dbfd 100644
--- a/crates/vfs/src/file_set.rs
+++ b/crates/vfs/src/file_set.rs
@@ -9,6 +9,7 @@ use rustc_hash::FxHashMap;
9 9
10use crate::{AnchoredPath, FileId, Vfs, VfsPath}; 10use crate::{AnchoredPath, FileId, Vfs, VfsPath};
11 11
12/// A set of [`VfsPath`]s identified by [`FileId`]s.
12#[derive(Default, Clone, Eq, PartialEq)] 13#[derive(Default, Clone, Eq, PartialEq)]
13pub struct FileSet { 14pub struct FileSet {
14 files: FxHashMap<VfsPath, FileId>, 15 files: FxHashMap<VfsPath, FileId>,
@@ -16,9 +17,15 @@ pub struct FileSet {
16} 17}
17 18
18impl FileSet { 19impl FileSet {
20 /// Returns the number of stored paths.
19 pub fn len(&self) -> usize { 21 pub fn len(&self) -> usize {
20 self.files.len() 22 self.files.len()
21 } 23 }
24
25 /// Get the id of the file corresponding to `path`.
26 ///
27 /// If either `path`'s [`anchor`](AnchoredPath::anchor) or the resolved path is not in
28 /// the set, returns [`None`].
22 pub fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> { 29 pub fn resolve_path(&self, path: AnchoredPath<'_>) -> Option<FileId> {
23 let mut base = self.paths[&path.anchor].clone(); 30 let mut base = self.paths[&path.anchor].clone();
24 base.pop(); 31 base.pop();
@@ -26,19 +33,26 @@ impl FileSet {
26 self.files.get(&path).copied() 33 self.files.get(&path).copied()
27 } 34 }
28 35
36 /// Get the id corresponding to `path` if it exists in the set.
29 pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> { 37 pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> {
30 self.files.get(path) 38 self.files.get(path)
31 } 39 }
32 40
41 /// Get the path corresponding to `file` if it exists in the set.
33 pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> { 42 pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> {
34 self.paths.get(file) 43 self.paths.get(file)
35 } 44 }
36 45
46 /// Insert the `file_id, path` pair into the set.
47 ///
48 /// # Note
49 /// Multiple [`FileId`] can be mapped to the same [`VfsPath`], and vice-versa.
37 pub fn insert(&mut self, file_id: FileId, path: VfsPath) { 50 pub fn insert(&mut self, file_id: FileId, path: VfsPath) {
38 self.files.insert(path.clone(), file_id); 51 self.files.insert(path.clone(), file_id);
39 self.paths.insert(file_id, path); 52 self.paths.insert(file_id, path);
40 } 53 }
41 54
55 /// Iterate over this set's ids.
42 pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ { 56 pub fn iter(&self) -> impl Iterator<Item = FileId> + '_ {
43 self.paths.keys().copied() 57 self.paths.keys().copied()
44 } 58 }
@@ -50,6 +64,23 @@ impl fmt::Debug for FileSet {
50 } 64 }
51} 65}
52 66
67/// This contains path prefixes to partition a [`Vfs`] into [`FileSet`]s.
68///
69/// # Example
70/// ```rust
71/// # use vfs::{file_set::FileSetConfigBuilder, VfsPath, Vfs};
72/// let mut builder = FileSetConfigBuilder::default();
73/// builder.add_file_set(vec![VfsPath::new_virtual_path("/src".to_string())]);
74/// let config = builder.build();
75/// let mut file_system = Vfs::default();
76/// file_system.set_file_contents(VfsPath::new_virtual_path("/src/main.rs".to_string()), Some(vec![]));
77/// file_system.set_file_contents(VfsPath::new_virtual_path("/src/lib.rs".to_string()), Some(vec![]));
78/// file_system.set_file_contents(VfsPath::new_virtual_path("/build.rs".to_string()), Some(vec![]));
79/// // contains the sets :
80/// // { "/src/main.rs", "/src/lib.rs" }
81/// // { "build.rs" }
82/// let sets = config.partition(&file_system);
83/// ```
53#[derive(Debug)] 84#[derive(Debug)]
54pub struct FileSetConfig { 85pub struct FileSetConfig {
55 n_file_sets: usize, 86 n_file_sets: usize,
@@ -63,9 +94,14 @@ impl Default for FileSetConfig {
63} 94}
64 95
65impl FileSetConfig { 96impl FileSetConfig {
97 /// Returns a builder for `FileSetConfig`.
66 pub fn builder() -> FileSetConfigBuilder { 98 pub fn builder() -> FileSetConfigBuilder {
67 FileSetConfigBuilder::default() 99 FileSetConfigBuilder::default()
68 } 100 }
101
102 /// Partition `vfs` into `FileSet`s.
103 ///
104 /// Creates a new [`FileSet`] for every set of prefixes in `self`.
69 pub fn partition(&self, vfs: &Vfs) -> Vec<FileSet> { 105 pub fn partition(&self, vfs: &Vfs) -> Vec<FileSet> {
70 let mut scratch_space = Vec::new(); 106 let mut scratch_space = Vec::new();
71 let mut res = vec![FileSet::default(); self.len()]; 107 let mut res = vec![FileSet::default(); self.len()];
@@ -91,6 +127,7 @@ impl FileSetConfig {
91 } 127 }
92} 128}
93 129
130/// Builder for [`FileSetConfig`].
94pub struct FileSetConfigBuilder { 131pub struct FileSetConfigBuilder {
95 roots: Vec<Vec<VfsPath>>, 132 roots: Vec<Vec<VfsPath>>,
96} 133}
@@ -102,12 +139,17 @@ impl Default for FileSetConfigBuilder {
102} 139}
103 140
104impl FileSetConfigBuilder { 141impl FileSetConfigBuilder {
142 /// Returns the number of sets currently held.
105 pub fn len(&self) -> usize { 143 pub fn len(&self) -> usize {
106 self.roots.len() 144 self.roots.len()
107 } 145 }
146
147 /// Add a new set of paths prefixes.
108 pub fn add_file_set(&mut self, roots: Vec<VfsPath>) { 148 pub fn add_file_set(&mut self, roots: Vec<VfsPath>) {
109 self.roots.push(roots) 149 self.roots.push(roots)
110 } 150 }
151
152 /// Build the `FileSetConfig`.
111 pub fn build(self) -> FileSetConfig { 153 pub fn build(self) -> FileSetConfig {
112 let n_file_sets = self.roots.len() + 1; 154 let n_file_sets = self.roots.len() + 1;
113 let map = { 155 let map = {
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs
index 2b7b14524..bae2c6118 100644
--- a/crates/vfs/src/lib.rs
+++ b/crates/vfs/src/lib.rs
@@ -53,9 +53,15 @@ pub use crate::{
53}; 53};
54pub use paths::{AbsPath, AbsPathBuf}; 54pub use paths::{AbsPath, AbsPathBuf};
55 55
56/// Handle to a file in [`Vfs`]
57///
58/// Most functions in rust-analyzer use this when they need to refer to a file.
56#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] 59#[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)]
57pub struct FileId(pub u32); 60pub struct FileId(pub u32);
58 61
62/// Storage for all files read by rust-analyzer.
63///
64/// For more informations see the [crate-level](crate) documentation.
59#[derive(Default)] 65#[derive(Default)]
60pub struct Vfs { 66pub struct Vfs {
61 interner: PathInterner, 67 interner: PathInterner,
@@ -63,40 +69,73 @@ pub struct Vfs {
63 changes: Vec<ChangedFile>, 69 changes: Vec<ChangedFile>,
64} 70}
65 71
72/// Changed file in the [`Vfs`].
66pub struct ChangedFile { 73pub struct ChangedFile {
74 /// Id of the changed file
67 pub file_id: FileId, 75 pub file_id: FileId,
76 /// Kind of change
68 pub change_kind: ChangeKind, 77 pub change_kind: ChangeKind,
69} 78}
70 79
71impl ChangedFile { 80impl ChangedFile {
81 /// Returns `true` if the change is not [`Delete`](ChangeKind::Delete).
72 pub fn exists(&self) -> bool { 82 pub fn exists(&self) -> bool {
73 self.change_kind != ChangeKind::Delete 83 self.change_kind != ChangeKind::Delete
74 } 84 }
85
86 /// Returns `true` if the change is [`Create`](ChangeKind::Create) or
87 /// [`Delete`](ChangeKind::Delete).
75 pub fn is_created_or_deleted(&self) -> bool { 88 pub fn is_created_or_deleted(&self) -> bool {
76 matches!(self.change_kind, ChangeKind::Create | ChangeKind::Delete) 89 matches!(self.change_kind, ChangeKind::Create | ChangeKind::Delete)
77 } 90 }
78} 91}
79 92
93/// Kind of [file change](ChangedFile).
80#[derive(Eq, PartialEq, Copy, Clone, Debug)] 94#[derive(Eq, PartialEq, Copy, Clone, Debug)]
81pub enum ChangeKind { 95pub enum ChangeKind {
96 /// The file was (re-)created
82 Create, 97 Create,
98 /// The file was modified
83 Modify, 99 Modify,
100 /// The file was deleted
84 Delete, 101 Delete,
85} 102}
86 103
87impl Vfs { 104impl Vfs {
105 /// Amount of files currently stored.
106 ///
107 /// Note that this includes deleted files.
88 pub fn len(&self) -> usize { 108 pub fn len(&self) -> usize {
89 self.data.len() 109 self.data.len()
90 } 110 }
111
112 /// Id of the given path if it exists in the `Vfs` and is not deleted.
91 pub fn file_id(&self, path: &VfsPath) -> Option<FileId> { 113 pub fn file_id(&self, path: &VfsPath) -> Option<FileId> {
92 self.interner.get(path).filter(|&it| self.get(it).is_some()) 114 self.interner.get(path).filter(|&it| self.get(it).is_some())
93 } 115 }
116
117 /// File path corresponding to the given `file_id`.
118 ///
119 /// # Panics
120 ///
121 /// Panics if the id is not present in the `Vfs`.
94 pub fn file_path(&self, file_id: FileId) -> VfsPath { 122 pub fn file_path(&self, file_id: FileId) -> VfsPath {
95 self.interner.lookup(file_id).clone() 123 self.interner.lookup(file_id).clone()
96 } 124 }
125
126 /// File content corresponding to the given `file_id`.
127 ///
128 /// # Panics
129 ///
130 /// Panics if the id is not present in the `Vfs`, or if the corresponding file is
131 /// deleted.
97 pub fn file_contents(&self, file_id: FileId) -> &[u8] { 132 pub fn file_contents(&self, file_id: FileId) -> &[u8] {
98 self.get(file_id).as_deref().unwrap() 133 self.get(file_id).as_deref().unwrap()
99 } 134 }
135
136 /// Returns an iterator over the stored ids and their corresponding paths.
137 ///
138 /// This will skip deleted files.
100 pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ { 139 pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ {
101 (0..self.data.len()) 140 (0..self.data.len())
102 .map(|it| FileId(it as u32)) 141 .map(|it| FileId(it as u32))
@@ -106,6 +145,13 @@ impl Vfs {
106 (file_id, path) 145 (file_id, path)
107 }) 146 })
108 } 147 }
148
149 /// Update the `path` with the given `contents`. `None` means the file was deleted.
150 ///
151 /// Returns `true` if the file was modified, and saves the [change](ChangedFile).
152 ///
153 /// If the path does not currently exists in the `Vfs`, allocates a new
154 /// [`FileId`] for it.
109 pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) -> bool { 155 pub fn set_file_contents(&mut self, path: VfsPath, contents: Option<Vec<u8>>) -> bool {
110 let file_id = self.alloc_file_id(path); 156 let file_id = self.alloc_file_id(path);
111 let change_kind = match (&self.get(file_id), &contents) { 157 let change_kind = match (&self.get(file_id), &contents) {
@@ -120,9 +166,13 @@ impl Vfs {
120 self.changes.push(ChangedFile { file_id, change_kind }); 166 self.changes.push(ChangedFile { file_id, change_kind });
121 true 167 true
122 } 168 }
169
170 /// Returns `true` if the `Vfs` contains [changes](ChangedFile).
123 pub fn has_changes(&self) -> bool { 171 pub fn has_changes(&self) -> bool {
124 !self.changes.is_empty() 172 !self.changes.is_empty()
125 } 173 }
174
175 /// Drain and returns all the changes in the `Vfs`.
126 pub fn take_changes(&mut self) -> Vec<ChangedFile> { 176 pub fn take_changes(&mut self) -> Vec<ChangedFile> {
127 mem::take(&mut self.changes) 177 mem::take(&mut self.changes)
128 } 178 }
diff --git a/crates/vfs/src/loader.rs b/crates/vfs/src/loader.rs
index 40cf96020..399015043 100644
--- a/crates/vfs/src/loader.rs
+++ b/crates/vfs/src/loader.rs
@@ -3,9 +3,12 @@ use std::fmt;
3 3
4use paths::{AbsPath, AbsPathBuf}; 4use paths::{AbsPath, AbsPathBuf};
5 5
6/// A set of files on the file system.
6#[derive(Debug, Clone)] 7#[derive(Debug, Clone)]
7pub enum Entry { 8pub enum Entry {
9 /// The `Entry` is represented by a raw set of files.
8 Files(Vec<AbsPathBuf>), 10 Files(Vec<AbsPathBuf>),
11 /// The `Entry` is represented by `Directories`.
9 Directories(Directories), 12 Directories(Directories),
10} 13}
11 14
@@ -17,6 +20,8 @@ pub enum Entry {
17/// * it is not under `exclude` path 20/// * it is not under `exclude` path
18/// 21///
19/// If many include/exclude paths match, the longest one wins. 22/// If many include/exclude paths match, the longest one wins.
23///
24/// If a path is in both `include` and `exclude`, the `exclude` one wins.
20#[derive(Debug, Clone, Default)] 25#[derive(Debug, Clone, Default)]
21pub struct Directories { 26pub struct Directories {
22 pub extensions: Vec<String>, 27 pub extensions: Vec<String>,
@@ -24,45 +29,99 @@ pub struct Directories {
24 pub exclude: Vec<AbsPathBuf>, 29 pub exclude: Vec<AbsPathBuf>,
25} 30}
26 31
32/// [`Handle`]'s configuration.
27#[derive(Debug)] 33#[derive(Debug)]
28pub struct Config { 34pub struct Config {
35 /// Set of initially loaded files.
29 pub load: Vec<Entry>, 36 pub load: Vec<Entry>,
37 /// Index of watched entries in `load`.
38 ///
39 /// If a path in a watched entry is modified,the [`Handle`] should notify it.
30 pub watch: Vec<usize>, 40 pub watch: Vec<usize>,
31} 41}
32 42
43/// Message about an action taken by a [`Handle`].
33pub enum Message { 44pub enum Message {
45 /// Indicate a gradual progress.
46 ///
47 /// This is supposed to be the number of loaded files.
34 Progress { n_total: usize, n_done: usize }, 48 Progress { n_total: usize, n_done: usize },
49 /// The handle loaded the following files' content.
35 Loaded { files: Vec<(AbsPathBuf, Option<Vec<u8>>)> }, 50 Loaded { files: Vec<(AbsPathBuf, Option<Vec<u8>>)> },
36} 51}
37 52
53/// Type that will receive [`Messages`](Message) from a [`Handle`].
38pub type Sender = Box<dyn Fn(Message) + Send>; 54pub type Sender = Box<dyn Fn(Message) + Send>;
39 55
56/// Interface for reading and watching files.
40pub trait Handle: fmt::Debug { 57pub trait Handle: fmt::Debug {
58 /// Spawn a new handle with the given `sender`.
41 fn spawn(sender: Sender) -> Self 59 fn spawn(sender: Sender) -> Self
42 where 60 where
43 Self: Sized; 61 Self: Sized;
62
63 /// Set this handle's configuration.
44 fn set_config(&mut self, config: Config); 64 fn set_config(&mut self, config: Config);
65
66 /// The file's content at `path` has been modified, and should be reloaded.
45 fn invalidate(&mut self, path: AbsPathBuf); 67 fn invalidate(&mut self, path: AbsPathBuf);
68
69 /// Load the content of the given file, returning [`None`] if it does not
70 /// exists.
46 fn load_sync(&mut self, path: &AbsPath) -> Option<Vec<u8>>; 71 fn load_sync(&mut self, path: &AbsPath) -> Option<Vec<u8>>;
47} 72}
48 73
49impl Entry { 74impl Entry {
75 /// Returns:
76 /// ```text
77 /// Entry::Directories(Directories {
78 /// extensions: ["rs"],
79 /// include: [base],
80 /// exclude: [base/.git],
81 /// })
82 /// ```
50 pub fn rs_files_recursively(base: AbsPathBuf) -> Entry { 83 pub fn rs_files_recursively(base: AbsPathBuf) -> Entry {
51 Entry::Directories(dirs(base, &[".git"])) 84 Entry::Directories(dirs(base, &[".git"]))
52 } 85 }
86
87 /// Returns:
88 /// ```text
89 /// Entry::Directories(Directories {
90 /// extensions: ["rs"],
91 /// include: [base],
92 /// exclude: [base/.git, base/target],
93 /// })
94 /// ```
53 pub fn local_cargo_package(base: AbsPathBuf) -> Entry { 95 pub fn local_cargo_package(base: AbsPathBuf) -> Entry {
54 Entry::Directories(dirs(base, &[".git", "target"])) 96 Entry::Directories(dirs(base, &[".git", "target"]))
55 } 97 }
98
99 /// Returns:
100 /// ```text
101 /// Entry::Directories(Directories {
102 /// extensions: ["rs"],
103 /// include: [base],
104 /// exclude: [base/.git, /tests, /examples, /benches],
105 /// })
106 /// ```
56 pub fn cargo_package_dependency(base: AbsPathBuf) -> Entry { 107 pub fn cargo_package_dependency(base: AbsPathBuf) -> Entry {
57 Entry::Directories(dirs(base, &[".git", "/tests", "/examples", "/benches"])) 108 Entry::Directories(dirs(base, &[".git", "/tests", "/examples", "/benches"]))
58 } 109 }
59 110
111 /// Returns `true` if `path` is included in `self`.
112 ///
113 /// See [`Directories::contains_file`].
60 pub fn contains_file(&self, path: &AbsPath) -> bool { 114 pub fn contains_file(&self, path: &AbsPath) -> bool {
61 match self { 115 match self {
62 Entry::Files(files) => files.iter().any(|it| it == path), 116 Entry::Files(files) => files.iter().any(|it| it == path),
63 Entry::Directories(dirs) => dirs.contains_file(path), 117 Entry::Directories(dirs) => dirs.contains_file(path),
64 } 118 }
65 } 119 }
120
121 /// Returns `true` if `path` is included in `self`.
122 ///
123 /// - If `self` is `Entry::Files`, returns `false`
124 /// - Else, see [`Directories::contains_dir`].
66 pub fn contains_dir(&self, path: &AbsPath) -> bool { 125 pub fn contains_dir(&self, path: &AbsPath) -> bool {
67 match self { 126 match self {
68 Entry::Files(_) => false, 127 Entry::Files(_) => false,
@@ -72,6 +131,7 @@ impl Entry {
72} 131}
73 132
74impl Directories { 133impl Directories {
134 /// Returns `true` if `path` is included in `self`.
75 pub fn contains_file(&self, path: &AbsPath) -> bool { 135 pub fn contains_file(&self, path: &AbsPath) -> bool {
76 let ext = path.extension().unwrap_or_default(); 136 let ext = path.extension().unwrap_or_default();
77 if self.extensions.iter().all(|it| it.as_str() != ext) { 137 if self.extensions.iter().all(|it| it.as_str() != ext) {
@@ -79,6 +139,11 @@ impl Directories {
79 } 139 }
80 self.includes_path(path) 140 self.includes_path(path)
81 } 141 }
142
143 /// Returns `true` if `path` is included in `self`.
144 ///
145 /// Since `path` is supposed to be a directory, this will not take extension
146 /// into account.
82 pub fn contains_dir(&self, path: &AbsPath) -> bool { 147 pub fn contains_dir(&self, path: &AbsPath) -> bool {
83 self.includes_path(path) 148 self.includes_path(path)
84 } 149 }
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs
index bd14911c9..74b6333e2 100644
--- a/crates/vfs/src/vfs_path.rs
+++ b/crates/vfs/src/vfs_path.rs
@@ -3,25 +3,37 @@ use std::fmt;
3 3
4use paths::{AbsPath, AbsPathBuf}; 4use paths::{AbsPath, AbsPathBuf};
5 5
6/// Path in [`Vfs`].
7///
6/// Long-term, we want to support files which do not reside in the file-system, 8/// Long-term, we want to support files which do not reside in the file-system,
7/// so we treat VfsPaths as opaque identifiers. 9/// so we treat `VfsPath`s as opaque identifiers.
10///
11/// [`Vfs`]: crate::Vfs
8#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] 12#[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)]
9pub struct VfsPath(VfsPathRepr); 13pub struct VfsPath(VfsPathRepr);
10 14
11impl VfsPath { 15impl VfsPath {
12 /// Creates an "in-memory" path from `/`-separates string. 16 /// Creates an "in-memory" path from `/`-separated string.
17 ///
13 /// This is most useful for testing, to avoid windows/linux differences 18 /// This is most useful for testing, to avoid windows/linux differences
19 ///
20 /// # Panics
21 ///
22 /// Panics if `path` does not start with `'/'`.
14 pub fn new_virtual_path(path: String) -> VfsPath { 23 pub fn new_virtual_path(path: String) -> VfsPath {
15 assert!(path.starts_with('/')); 24 assert!(path.starts_with('/'));
16 VfsPath(VfsPathRepr::VirtualPath(VirtualPath(path))) 25 VfsPath(VfsPathRepr::VirtualPath(VirtualPath(path)))
17 } 26 }
18 27
28 /// Returns the `AbsPath` representation of `self` if `self` is on the file system.
19 pub fn as_path(&self) -> Option<&AbsPath> { 29 pub fn as_path(&self) -> Option<&AbsPath> {
20 match &self.0 { 30 match &self.0 {
21 VfsPathRepr::PathBuf(it) => Some(it.as_path()), 31 VfsPathRepr::PathBuf(it) => Some(it.as_path()),
22 VfsPathRepr::VirtualPath(_) => None, 32 VfsPathRepr::VirtualPath(_) => None,
23 } 33 }
24 } 34 }
35
36 /// Creates a new `VfsPath` with `path` adjoined to `self`.
25 pub fn join(&self, path: &str) -> Option<VfsPath> { 37 pub fn join(&self, path: &str) -> Option<VfsPath> {
26 match &self.0 { 38 match &self.0 {
27 VfsPathRepr::PathBuf(it) => { 39 VfsPathRepr::PathBuf(it) => {
@@ -34,12 +46,30 @@ impl VfsPath {
34 } 46 }
35 } 47 }
36 } 48 }
49
50 /// Remove the last component of `self` if there is one.
51 ///
52 /// If `self` has no component, returns `false`; else returns `true`.
53 ///
54 /// # Example
55 ///
56 /// ```
57 /// # use vfs::{AbsPathBuf, VfsPath};
58 /// let mut path = VfsPath::from(AbsPathBuf::assert("/foo/bar".into()));
59 /// assert!(path.pop());
60 /// assert_eq!(path, VfsPath::from(AbsPathBuf::assert("/foo".into())));
61 /// assert!(path.pop());
62 /// assert_eq!(path, VfsPath::from(AbsPathBuf::assert("/".into())));
63 /// assert!(!path.pop());
64 /// ```
37 pub fn pop(&mut self) -> bool { 65 pub fn pop(&mut self) -> bool {
38 match &mut self.0 { 66 match &mut self.0 {
39 VfsPathRepr::PathBuf(it) => it.pop(), 67 VfsPathRepr::PathBuf(it) => it.pop(),
40 VfsPathRepr::VirtualPath(it) => it.pop(), 68 VfsPathRepr::VirtualPath(it) => it.pop(),
41 } 69 }
42 } 70 }
71
72 /// Returns `true` if `other` is a prefix of `self`.
43 pub fn starts_with(&self, other: &VfsPath) -> bool { 73 pub fn starts_with(&self, other: &VfsPath) -> bool {
44 match (&self.0, &other.0) { 74 match (&self.0, &other.0) {
45 (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.starts_with(rhs), 75 (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.starts_with(rhs),
@@ -48,6 +78,10 @@ impl VfsPath {
48 (VfsPathRepr::VirtualPath(_), _) => false, 78 (VfsPathRepr::VirtualPath(_), _) => false,
49 } 79 }
50 } 80 }
81
82 /// Returns the `VfsPath` without its final component, if there is one.
83 ///
84 /// Returns [`None`] if the path is a root or prefix.
51 pub fn parent(&self) -> Option<VfsPath> { 85 pub fn parent(&self) -> Option<VfsPath> {
52 let mut parent = self.clone(); 86 let mut parent = self.clone();
53 if parent.pop() { 87 if parent.pop() {
@@ -57,6 +91,7 @@ impl VfsPath {
57 } 91 }
58 } 92 }
59 93
94 /// Returns `self`'s base name and file extension.
60 pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { 95 pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> {
61 match &self.0 { 96 match &self.0 {
62 VfsPathRepr::PathBuf(p) => Some(( 97 VfsPathRepr::PathBuf(p) => Some((