aboutsummaryrefslogtreecommitdiff
path: root/crates/vfs/src
diff options
context:
space:
mode:
Diffstat (limited to 'crates/vfs/src')
-rw-r--r--crates/vfs/src/anchored_path.rs10
-rw-r--r--crates/vfs/src/file_set.rs57
-rw-r--r--crates/vfs/src/lib.rs70
-rw-r--r--crates/vfs/src/loader.rs80
-rw-r--r--crates/vfs/src/path_interner.rs14
-rw-r--r--crates/vfs/src/vfs_path.rs94
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.
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..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
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,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)]
54pub struct FileSetConfig { 85pub 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
65impl FileSetConfig { 101impl 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`].
94pub struct FileSetConfigBuilder { 142pub 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
104impl FileSetConfigBuilder { 152impl 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.
130struct PrefixOf<'a> { 186struct PrefixOf<'a> {
131 prefix_of: &'a [u8], 187 prefix_of: &'a [u8],
132} 188}
133 189
134impl<'a> PrefixOf<'a> { 190impl<'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};
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,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
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,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/// ```
108fn dirs(base: AbsPathBuf, exclude: &[&str]) -> Directories { 188fn 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
6use crate::{FileId, VfsPath}; 6use crate::{FileId, VfsPath};
7 7
8/// Structure to map between [`VfsPath`] and [`FileId`].
8#[derive(Default)] 9#[derive(Default)]
9pub(crate) struct PathInterner { 10pub(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
14impl PathInterner { 15impl 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
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((
@@ -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)]
228enum VfsPathRepr { 271enum 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)]
264struct VirtualPath(String); 310struct VirtualPath(String);
265 311
266impl VirtualPath { 312impl 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..],