diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/vfs/src/anchored_path.rs | 10 | ||||
-rw-r--r-- | crates/vfs/src/file_set.rs | 42 | ||||
-rw-r--r-- | crates/vfs/src/lib.rs | 50 | ||||
-rw-r--r-- | crates/vfs/src/loader.rs | 65 | ||||
-rw-r--r-- | crates/vfs/src/vfs_path.rs | 39 |
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. |
27 | use crate::FileId; | 27 | use 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)] |
30 | pub struct AnchoredPathBuf { | 33 | pub 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)] |
36 | pub struct AnchoredPath<'a> { | 44 | pub 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 | ||
10 | use crate::{AnchoredPath, FileId, Vfs, VfsPath}; | 10 | use 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)] |
13 | pub struct FileSet { | 14 | pub struct FileSet { |
14 | files: FxHashMap<VfsPath, FileId>, | 15 | files: FxHashMap<VfsPath, FileId>, |
@@ -16,9 +17,15 @@ pub struct FileSet { | |||
16 | } | 17 | } |
17 | 18 | ||
18 | impl FileSet { | 19 | impl 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)] |
54 | pub struct FileSetConfig { | 85 | pub struct FileSetConfig { |
55 | n_file_sets: usize, | 86 | n_file_sets: usize, |
@@ -63,9 +94,14 @@ impl Default for FileSetConfig { | |||
63 | } | 94 | } |
64 | 95 | ||
65 | impl FileSetConfig { | 96 | impl 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`]. | ||
94 | pub struct FileSetConfigBuilder { | 131 | pub 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 | ||
104 | impl FileSetConfigBuilder { | 141 | impl 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 | }; |
54 | pub use paths::{AbsPath, AbsPathBuf}; | 54 | pub 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)] |
57 | pub struct FileId(pub u32); | 60 | pub 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)] |
60 | pub struct Vfs { | 66 | pub 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`]. | ||
66 | pub struct ChangedFile { | 73 | pub 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 | ||
71 | impl ChangedFile { | 80 | impl 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)] |
81 | pub enum ChangeKind { | 95 | pub 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 | ||
87 | impl Vfs { | 104 | impl 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 | ||
4 | use paths::{AbsPath, AbsPathBuf}; | 4 | use paths::{AbsPath, AbsPathBuf}; |
5 | 5 | ||
6 | /// A set of files on the file system. | ||
6 | #[derive(Debug, Clone)] | 7 | #[derive(Debug, Clone)] |
7 | pub enum Entry { | 8 | pub 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)] |
21 | pub struct Directories { | 26 | pub 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)] |
28 | pub struct Config { | 34 | pub 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`]. | ||
33 | pub enum Message { | 44 | pub 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`]. | ||
38 | pub type Sender = Box<dyn Fn(Message) + Send>; | 54 | pub type Sender = Box<dyn Fn(Message) + Send>; |
39 | 55 | ||
56 | /// Interface for reading and watching files. | ||
40 | pub trait Handle: fmt::Debug { | 57 | pub 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 | ||
49 | impl Entry { | 74 | impl 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 | ||
74 | impl Directories { | 133 | impl 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 | ||
4 | use paths::{AbsPath, AbsPathBuf}; | 4 | use 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)] |
9 | pub struct VfsPath(VfsPathRepr); | 13 | pub struct VfsPath(VfsPathRepr); |
10 | 14 | ||
11 | impl VfsPath { | 15 | impl 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(( |