diff options
-rw-r--r-- | crates/vfs/src/anchored_path.rs | 10 | ||||
-rw-r--r-- | crates/vfs/src/file_set.rs | 57 | ||||
-rw-r--r-- | crates/vfs/src/lib.rs | 70 | ||||
-rw-r--r-- | crates/vfs/src/loader.rs | 80 | ||||
-rw-r--r-- | crates/vfs/src/path_interner.rs | 14 | ||||
-rw-r--r-- | crates/vfs/src/vfs_path.rs | 94 |
6 files changed, 321 insertions, 4 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..0a4590c8d 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,9 +64,31 @@ 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 { |
86 | /// Number of sets that `self` can partition a [`Vfs`] into. | ||
87 | /// | ||
88 | /// This should be the number of sets in `self.map` + 1 for files that don't fit in any | ||
89 | /// defined set. | ||
55 | n_file_sets: usize, | 90 | n_file_sets: usize, |
91 | /// Map from encoded paths to the set they belong to. | ||
56 | map: fst::Map<Vec<u8>>, | 92 | map: fst::Map<Vec<u8>>, |
57 | } | 93 | } |
58 | 94 | ||
@@ -63,9 +99,14 @@ impl Default for FileSetConfig { | |||
63 | } | 99 | } |
64 | 100 | ||
65 | impl FileSetConfig { | 101 | impl FileSetConfig { |
102 | /// Returns a builder for `FileSetConfig`. | ||
66 | pub fn builder() -> FileSetConfigBuilder { | 103 | pub fn builder() -> FileSetConfigBuilder { |
67 | FileSetConfigBuilder::default() | 104 | FileSetConfigBuilder::default() |
68 | } | 105 | } |
106 | |||
107 | /// Partition `vfs` into `FileSet`s. | ||
108 | /// | ||
109 | /// Creates a new [`FileSet`] for every set of prefixes in `self`. | ||
69 | pub fn partition(&self, vfs: &Vfs) -> Vec<FileSet> { | 110 | pub fn partition(&self, vfs: &Vfs) -> Vec<FileSet> { |
70 | let mut scratch_space = Vec::new(); | 111 | let mut scratch_space = Vec::new(); |
71 | let mut res = vec![FileSet::default(); self.len()]; | 112 | let mut res = vec![FileSet::default(); self.len()]; |
@@ -75,9 +116,15 @@ impl FileSetConfig { | |||
75 | } | 116 | } |
76 | res | 117 | res |
77 | } | 118 | } |
119 | |||
120 | /// Number of sets that `self` can partition a [`Vfs`] into. | ||
78 | fn len(&self) -> usize { | 121 | fn len(&self) -> usize { |
79 | self.n_file_sets | 122 | self.n_file_sets |
80 | } | 123 | } |
124 | |||
125 | /// Returns the set index for the given `path`. | ||
126 | /// | ||
127 | /// `scratch_space` is used as a buffer and will be entirely replaced. | ||
81 | fn classify(&self, path: &VfsPath, scratch_space: &mut Vec<u8>) -> usize { | 128 | fn classify(&self, path: &VfsPath, scratch_space: &mut Vec<u8>) -> usize { |
82 | scratch_space.clear(); | 129 | scratch_space.clear(); |
83 | path.encode(scratch_space); | 130 | path.encode(scratch_space); |
@@ -91,6 +138,7 @@ impl FileSetConfig { | |||
91 | } | 138 | } |
92 | } | 139 | } |
93 | 140 | ||
141 | /// Builder for [`FileSetConfig`]. | ||
94 | pub struct FileSetConfigBuilder { | 142 | pub struct FileSetConfigBuilder { |
95 | roots: Vec<Vec<VfsPath>>, | 143 | roots: Vec<Vec<VfsPath>>, |
96 | } | 144 | } |
@@ -102,12 +150,17 @@ impl Default for FileSetConfigBuilder { | |||
102 | } | 150 | } |
103 | 151 | ||
104 | impl FileSetConfigBuilder { | 152 | impl FileSetConfigBuilder { |
153 | /// Returns the number of sets currently held. | ||
105 | pub fn len(&self) -> usize { | 154 | pub fn len(&self) -> usize { |
106 | self.roots.len() | 155 | self.roots.len() |
107 | } | 156 | } |
157 | |||
158 | /// Add a new set of paths prefixes. | ||
108 | pub fn add_file_set(&mut self, roots: Vec<VfsPath>) { | 159 | pub fn add_file_set(&mut self, roots: Vec<VfsPath>) { |
109 | self.roots.push(roots) | 160 | self.roots.push(roots) |
110 | } | 161 | } |
162 | |||
163 | /// Build the `FileSetConfig`. | ||
111 | pub fn build(self) -> FileSetConfig { | 164 | pub fn build(self) -> FileSetConfig { |
112 | let n_file_sets = self.roots.len() + 1; | 165 | let n_file_sets = self.roots.len() + 1; |
113 | let map = { | 166 | let map = { |
@@ -127,11 +180,15 @@ impl FileSetConfigBuilder { | |||
127 | } | 180 | } |
128 | } | 181 | } |
129 | 182 | ||
183 | /// Implements [`fst::Automaton`] | ||
184 | /// | ||
185 | /// It will match if `prefix_of` is a prefix of the given data. | ||
130 | struct PrefixOf<'a> { | 186 | struct PrefixOf<'a> { |
131 | prefix_of: &'a [u8], | 187 | prefix_of: &'a [u8], |
132 | } | 188 | } |
133 | 189 | ||
134 | impl<'a> PrefixOf<'a> { | 190 | impl<'a> PrefixOf<'a> { |
191 | /// Creates a new `PrefixOf` from the given slice. | ||
135 | fn new(prefix_of: &'a [u8]) -> Self { | 192 | fn new(prefix_of: &'a [u8]) -> Self { |
136 | Self { prefix_of } | 193 | Self { prefix_of } |
137 | } | 194 | } |
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs index 2b7b14524..e075d752b 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,12 +166,24 @@ 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 | } |
179 | |||
180 | /// Returns the id associated with `path` | ||
181 | /// | ||
182 | /// - If `path` does not exists in the `Vfs`, allocate a new id for it, associated with a | ||
183 | /// deleted file; | ||
184 | /// - Else, returns `path`'s id. | ||
185 | /// | ||
186 | /// Does not record a change. | ||
129 | fn alloc_file_id(&mut self, path: VfsPath) -> FileId { | 187 | fn alloc_file_id(&mut self, path: VfsPath) -> FileId { |
130 | let file_id = self.interner.intern(path); | 188 | let file_id = self.interner.intern(path); |
131 | let idx = file_id.0 as usize; | 189 | let idx = file_id.0 as usize; |
@@ -133,9 +191,21 @@ impl Vfs { | |||
133 | self.data.resize_with(len, || None); | 191 | self.data.resize_with(len, || None); |
134 | file_id | 192 | file_id |
135 | } | 193 | } |
194 | |||
195 | /// Returns the content associated with the given `file_id`. | ||
196 | /// | ||
197 | /// # Panics | ||
198 | /// | ||
199 | /// Panics if no file is associated to that id. | ||
136 | fn get(&self, file_id: FileId) -> &Option<Vec<u8>> { | 200 | fn get(&self, file_id: FileId) -> &Option<Vec<u8>> { |
137 | &self.data[file_id.0 as usize] | 201 | &self.data[file_id.0 as usize] |
138 | } | 202 | } |
203 | |||
204 | /// Mutably returns the content associated with the given `file_id`. | ||
205 | /// | ||
206 | /// # Panics | ||
207 | /// | ||
208 | /// Panics if no file is associated to that id. | ||
139 | fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> { | 209 | fn get_mut(&mut self, file_id: FileId) -> &mut Option<Vec<u8>> { |
140 | &mut self.data[file_id.0 as usize] | 210 | &mut self.data[file_id.0 as usize] |
141 | } | 211 | } |
diff --git a/crates/vfs/src/loader.rs b/crates/vfs/src/loader.rs index 40cf96020..d3bdae562 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,9 +139,21 @@ 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 | } |
150 | |||
151 | /// Returns `true` if `path` is included in `self`. | ||
152 | /// | ||
153 | /// It is included if | ||
154 | /// - An element in `self.include` is a prefix of `path`. | ||
155 | /// - This path is longer than any element in `self.exclude` that is a prefix | ||
156 | /// of `path`. In case of equality, exclusion wins. | ||
85 | fn includes_path(&self, path: &AbsPath) -> bool { | 157 | fn includes_path(&self, path: &AbsPath) -> bool { |
86 | let mut include: Option<&AbsPathBuf> = None; | 158 | let mut include: Option<&AbsPathBuf> = None; |
87 | for incl in &self.include { | 159 | for incl in &self.include { |
@@ -105,6 +177,14 @@ impl Directories { | |||
105 | } | 177 | } |
106 | } | 178 | } |
107 | 179 | ||
180 | /// Returns : | ||
181 | /// ```text | ||
182 | /// Directories { | ||
183 | /// extensions: ["rs"], | ||
184 | /// include: [base], | ||
185 | /// exclude: [base/<exclude>], | ||
186 | /// } | ||
187 | /// ``` | ||
108 | fn dirs(base: AbsPathBuf, exclude: &[&str]) -> Directories { | 188 | fn dirs(base: AbsPathBuf, exclude: &[&str]) -> Directories { |
109 | let exclude = exclude.iter().map(|it| base.join(it)).collect::<Vec<_>>(); | 189 | let exclude = exclude.iter().map(|it| base.join(it)).collect::<Vec<_>>(); |
110 | Directories { extensions: vec!["rs".to_string()], include: vec![base], exclude } | 190 | Directories { extensions: vec!["rs".to_string()], include: vec![base], exclude } |
diff --git a/crates/vfs/src/path_interner.rs b/crates/vfs/src/path_interner.rs index 4f70d61e8..2189e5e25 100644 --- a/crates/vfs/src/path_interner.rs +++ b/crates/vfs/src/path_interner.rs | |||
@@ -5,6 +5,7 @@ use rustc_hash::FxHashMap; | |||
5 | 5 | ||
6 | use crate::{FileId, VfsPath}; | 6 | use crate::{FileId, VfsPath}; |
7 | 7 | ||
8 | /// Structure to map between [`VfsPath`] and [`FileId`]. | ||
8 | #[derive(Default)] | 9 | #[derive(Default)] |
9 | pub(crate) struct PathInterner { | 10 | pub(crate) struct PathInterner { |
10 | map: FxHashMap<VfsPath, FileId>, | 11 | map: FxHashMap<VfsPath, FileId>, |
@@ -12,9 +13,17 @@ pub(crate) struct PathInterner { | |||
12 | } | 13 | } |
13 | 14 | ||
14 | impl PathInterner { | 15 | impl PathInterner { |
16 | /// Get the id corresponding to `path`. | ||
17 | /// | ||
18 | /// If `path` does not exists in `self`, returns [`None`]. | ||
15 | pub(crate) fn get(&self, path: &VfsPath) -> Option<FileId> { | 19 | pub(crate) fn get(&self, path: &VfsPath) -> Option<FileId> { |
16 | self.map.get(path).copied() | 20 | self.map.get(path).copied() |
17 | } | 21 | } |
22 | |||
23 | /// Insert `path` in `self`. | ||
24 | /// | ||
25 | /// - If `path` already exists in `self`, returns its associated id; | ||
26 | /// - Else, returns a newly allocated id. | ||
18 | pub(crate) fn intern(&mut self, path: VfsPath) -> FileId { | 27 | pub(crate) fn intern(&mut self, path: VfsPath) -> FileId { |
19 | if let Some(id) = self.get(&path) { | 28 | if let Some(id) = self.get(&path) { |
20 | return id; | 29 | return id; |
@@ -25,6 +34,11 @@ impl PathInterner { | |||
25 | id | 34 | id |
26 | } | 35 | } |
27 | 36 | ||
37 | /// Returns the path corresponding to `id`. | ||
38 | /// | ||
39 | /// # Panics | ||
40 | /// | ||
41 | /// Panics if `id` does not exists in `self`. | ||
28 | pub(crate) fn lookup(&self, id: FileId) -> &VfsPath { | 42 | pub(crate) fn lookup(&self, id: FileId) -> &VfsPath { |
29 | &self.vec[id.0 as usize] | 43 | &self.vec[id.0 as usize] |
30 | } | 44 | } |
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs index bd14911c9..2b3d7fd84 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(( |
@@ -67,7 +102,14 @@ impl VfsPath { | |||
67 | } | 102 | } |
68 | } | 103 | } |
69 | 104 | ||
70 | // Don't make this `pub` | 105 | /// **Don't make this `pub`** |
106 | /// | ||
107 | /// Encode the path in the given buffer. | ||
108 | /// | ||
109 | /// The encoding will be `0` if [`AbsPathBuf`], `1` if [`VirtualPath`], followed | ||
110 | /// by `self`'s representation. | ||
111 | /// | ||
112 | /// Note that this encoding is dependent on the operating system. | ||
71 | pub(crate) fn encode(&self, buf: &mut Vec<u8>) { | 113 | pub(crate) fn encode(&self, buf: &mut Vec<u8>) { |
72 | let tag = match &self.0 { | 114 | let tag = match &self.0 { |
73 | VfsPathRepr::PathBuf(_) => 0, | 115 | VfsPathRepr::PathBuf(_) => 0, |
@@ -224,6 +266,7 @@ mod windows_paths { | |||
224 | } | 266 | } |
225 | } | 267 | } |
226 | 268 | ||
269 | /// Internal, private representation of [`VfsPath`]. | ||
227 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | 270 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |
228 | enum VfsPathRepr { | 271 | enum VfsPathRepr { |
229 | PathBuf(AbsPathBuf), | 272 | PathBuf(AbsPathBuf), |
@@ -260,13 +303,34 @@ impl fmt::Debug for VfsPathRepr { | |||
260 | } | 303 | } |
261 | } | 304 | } |
262 | 305 | ||
306 | /// `/`-separated virtual path. | ||
307 | /// | ||
308 | /// This is used to describe files that do not reside on the file system. | ||
263 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | 309 | #[derive(Debug, Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |
264 | struct VirtualPath(String); | 310 | struct VirtualPath(String); |
265 | 311 | ||
266 | impl VirtualPath { | 312 | impl VirtualPath { |
313 | /// Returns `true` if `other` is a prefix of `self` (as strings). | ||
267 | fn starts_with(&self, other: &VirtualPath) -> bool { | 314 | fn starts_with(&self, other: &VirtualPath) -> bool { |
268 | self.0.starts_with(&other.0) | 315 | self.0.starts_with(&other.0) |
269 | } | 316 | } |
317 | |||
318 | /// Remove the last component of `self`. | ||
319 | /// | ||
320 | /// This will find the last `'/'` in `self`, and remove everything after it, | ||
321 | /// including the `'/'`. | ||
322 | /// | ||
323 | /// If `self` contains no `'/'`, returns `false`; else returns `true`. | ||
324 | /// | ||
325 | /// # Example | ||
326 | /// | ||
327 | /// ```rust,ignore | ||
328 | /// let mut path = VirtualPath("/foo/bar".to_string()); | ||
329 | /// path.pop(); | ||
330 | /// assert_eq!(path.0, "/foo"); | ||
331 | /// path.pop(); | ||
332 | /// assert_eq!(path.0, ""); | ||
333 | /// ``` | ||
270 | fn pop(&mut self) -> bool { | 334 | fn pop(&mut self) -> bool { |
271 | let pos = match self.0.rfind('/') { | 335 | let pos = match self.0.rfind('/') { |
272 | Some(pos) => pos, | 336 | Some(pos) => pos, |
@@ -275,6 +339,17 @@ impl VirtualPath { | |||
275 | self.0 = self.0[..pos].to_string(); | 339 | self.0 = self.0[..pos].to_string(); |
276 | true | 340 | true |
277 | } | 341 | } |
342 | |||
343 | /// Append the given *relative* path `path` to `self`. | ||
344 | /// | ||
345 | /// This will resolve any leading `"../"` in `path` before appending it. | ||
346 | /// | ||
347 | /// Returns [`None`] if `path` has more leading `"../"` than the number of | ||
348 | /// components in `self`. | ||
349 | /// | ||
350 | /// # Notes | ||
351 | /// | ||
352 | /// In practice, appending here means `self/path` as strings. | ||
278 | fn join(&self, mut path: &str) -> Option<VirtualPath> { | 353 | fn join(&self, mut path: &str) -> Option<VirtualPath> { |
279 | let mut res = self.clone(); | 354 | let mut res = self.clone(); |
280 | while path.starts_with("../") { | 355 | while path.starts_with("../") { |
@@ -287,7 +362,18 @@ impl VirtualPath { | |||
287 | Some(res) | 362 | Some(res) |
288 | } | 363 | } |
289 | 364 | ||
290 | pub(crate) fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { | 365 | /// Returns `self`'s base name and file extension. |
366 | /// | ||
367 | /// # Returns | ||
368 | /// - `None` if `self` ends with `"//"`. | ||
369 | /// - `Some((name, None))` if `self`'s base contains no `.`, or only one `.` at | ||
370 | /// the start. | ||
371 | /// - `Some((name, Some(extension))` else. | ||
372 | /// | ||
373 | /// # Note | ||
374 | /// The extension will not contains `.`. This means `"/foo/bar.baz.rs"` will | ||
375 | /// return `Some(("bar.baz", Some("rs"))`. | ||
376 | fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { | ||
291 | let file_path = if self.0.ends_with('/') { &self.0[..&self.0.len() - 1] } else { &self.0 }; | 377 | let file_path = if self.0.ends_with('/') { &self.0[..&self.0.len() - 1] } else { &self.0 }; |
292 | let file_name = match file_path.rfind('/') { | 378 | let file_name = match file_path.rfind('/') { |
293 | Some(position) => &file_path[position + 1..], | 379 | Some(position) => &file_path[position + 1..], |