diff options
Diffstat (limited to 'crates/vfs')
-rw-r--r-- | crates/vfs/Cargo.toml | 5 | ||||
-rw-r--r-- | crates/vfs/src/file_set.rs | 135 | ||||
-rw-r--r-- | crates/vfs/src/lib.rs | 6 | ||||
-rw-r--r-- | crates/vfs/src/loader.rs | 78 | ||||
-rw-r--r-- | crates/vfs/src/vfs_path.rs | 31 |
5 files changed, 222 insertions, 33 deletions
diff --git a/crates/vfs/Cargo.toml b/crates/vfs/Cargo.toml index 263069002..b74cdb7ff 100644 --- a/crates/vfs/Cargo.toml +++ b/crates/vfs/Cargo.toml | |||
@@ -3,8 +3,13 @@ name = "vfs" | |||
3 | version = "0.1.0" | 3 | version = "0.1.0" |
4 | authors = ["rust-analyzer developers"] | 4 | authors = ["rust-analyzer developers"] |
5 | edition = "2018" | 5 | edition = "2018" |
6 | license = "MIT OR Apache-2.0" | ||
7 | |||
8 | [lib] | ||
9 | doctest = false | ||
6 | 10 | ||
7 | [dependencies] | 11 | [dependencies] |
8 | rustc-hash = "1.0" | 12 | rustc-hash = "1.0" |
13 | fst = "0.4" | ||
9 | 14 | ||
10 | paths = { path = "../paths" } | 15 | paths = { path = "../paths" } |
diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs index d0ddeafe7..e9196fcd2 100644 --- a/crates/vfs/src/file_set.rs +++ b/crates/vfs/src/file_set.rs | |||
@@ -2,8 +2,9 @@ | |||
2 | //! | 2 | //! |
3 | //! Files which do not belong to any explicitly configured `FileSet` belong to | 3 | //! Files which do not belong to any explicitly configured `FileSet` belong to |
4 | //! the default `FileSet`. | 4 | //! the default `FileSet`. |
5 | use std::{fmt, iter}; | 5 | use std::fmt; |
6 | 6 | ||
7 | use fst::{IntoStreamer, Streamer}; | ||
7 | use rustc_hash::FxHashMap; | 8 | use rustc_hash::FxHashMap; |
8 | 9 | ||
9 | use crate::{FileId, Vfs, VfsPath}; | 10 | use crate::{FileId, Vfs, VfsPath}; |
@@ -15,6 +16,9 @@ pub struct FileSet { | |||
15 | } | 16 | } |
16 | 17 | ||
17 | impl FileSet { | 18 | impl FileSet { |
19 | pub fn len(&self) -> usize { | ||
20 | self.files.len() | ||
21 | } | ||
18 | pub fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> { | 22 | pub fn resolve_path(&self, anchor: FileId, path: &str) -> Option<FileId> { |
19 | let mut base = self.paths[&anchor].clone(); | 23 | let mut base = self.paths[&anchor].clone(); |
20 | base.pop(); | 24 | base.pop(); |
@@ -40,7 +44,7 @@ impl fmt::Debug for FileSet { | |||
40 | #[derive(Debug)] | 44 | #[derive(Debug)] |
41 | pub struct FileSetConfig { | 45 | pub struct FileSetConfig { |
42 | n_file_sets: usize, | 46 | n_file_sets: usize, |
43 | roots: Vec<(VfsPath, usize)>, | 47 | map: fst::Map<Vec<u8>>, |
44 | } | 48 | } |
45 | 49 | ||
46 | impl Default for FileSetConfig { | 50 | impl Default for FileSetConfig { |
@@ -54,26 +58,27 @@ impl FileSetConfig { | |||
54 | FileSetConfigBuilder::default() | 58 | FileSetConfigBuilder::default() |
55 | } | 59 | } |
56 | pub fn partition(&self, vfs: &Vfs) -> Vec<FileSet> { | 60 | pub fn partition(&self, vfs: &Vfs) -> Vec<FileSet> { |
61 | let mut scratch_space = Vec::new(); | ||
57 | let mut res = vec![FileSet::default(); self.len()]; | 62 | let mut res = vec![FileSet::default(); self.len()]; |
58 | for (file_id, path) in vfs.iter() { | 63 | for (file_id, path) in vfs.iter() { |
59 | let root = self.classify(&path); | 64 | let root = self.classify(&path, &mut scratch_space); |
60 | res[root].insert(file_id, path) | 65 | res[root].insert(file_id, path.clone()) |
61 | } | 66 | } |
62 | res | 67 | res |
63 | } | 68 | } |
64 | fn len(&self) -> usize { | 69 | fn len(&self) -> usize { |
65 | self.n_file_sets | 70 | self.n_file_sets |
66 | } | 71 | } |
67 | fn classify(&self, path: &VfsPath) -> usize { | 72 | fn classify(&self, path: &VfsPath, scratch_space: &mut Vec<u8>) -> usize { |
68 | let idx = match self.roots.binary_search_by(|(p, _)| p.cmp(path)) { | 73 | scratch_space.clear(); |
69 | Ok(it) => it, | 74 | path.encode(scratch_space); |
70 | Err(it) => it.saturating_sub(1), | 75 | let automaton = PrefixOf::new(scratch_space.as_slice()); |
71 | }; | 76 | let mut longest_prefix = self.len() - 1; |
72 | if path.starts_with(&self.roots[idx].0) { | 77 | let mut stream = self.map.search(automaton).into_stream(); |
73 | self.roots[idx].1 | 78 | while let Some((_, v)) = stream.next() { |
74 | } else { | 79 | longest_prefix = v as usize; |
75 | self.len() - 1 | ||
76 | } | 80 | } |
81 | longest_prefix | ||
77 | } | 82 | } |
78 | } | 83 | } |
79 | 84 | ||
@@ -96,13 +101,101 @@ impl FileSetConfigBuilder { | |||
96 | } | 101 | } |
97 | pub fn build(self) -> FileSetConfig { | 102 | pub fn build(self) -> FileSetConfig { |
98 | let n_file_sets = self.roots.len() + 1; | 103 | let n_file_sets = self.roots.len() + 1; |
99 | let mut roots: Vec<(VfsPath, usize)> = self | 104 | let map = { |
100 | .roots | 105 | let mut entries = Vec::new(); |
101 | .into_iter() | 106 | for (i, paths) in self.roots.into_iter().enumerate() { |
102 | .enumerate() | 107 | for p in paths { |
103 | .flat_map(|(i, paths)| paths.into_iter().zip(iter::repeat(i))) | 108 | let mut buf = Vec::new(); |
104 | .collect(); | 109 | p.encode(&mut buf); |
105 | roots.sort(); | 110 | entries.push((buf, i as u64)); |
106 | FileSetConfig { n_file_sets, roots } | 111 | } |
112 | } | ||
113 | entries.sort(); | ||
114 | entries.dedup_by(|(a, _), (b, _)| a == b); | ||
115 | fst::Map::from_iter(entries).unwrap() | ||
116 | }; | ||
117 | FileSetConfig { n_file_sets, map } | ||
118 | } | ||
119 | } | ||
120 | |||
121 | struct PrefixOf<'a> { | ||
122 | prefix_of: &'a [u8], | ||
123 | } | ||
124 | |||
125 | impl<'a> PrefixOf<'a> { | ||
126 | fn new(prefix_of: &'a [u8]) -> Self { | ||
127 | Self { prefix_of } | ||
128 | } | ||
129 | } | ||
130 | |||
131 | impl fst::Automaton for PrefixOf<'_> { | ||
132 | type State = usize; | ||
133 | fn start(&self) -> usize { | ||
134 | 0 | ||
135 | } | ||
136 | fn is_match(&self, &state: &usize) -> bool { | ||
137 | state != !0 | ||
138 | } | ||
139 | fn can_match(&self, &state: &usize) -> bool { | ||
140 | state != !0 | ||
141 | } | ||
142 | fn accept(&self, &state: &usize, byte: u8) -> usize { | ||
143 | if self.prefix_of.get(state) == Some(&byte) { | ||
144 | state + 1 | ||
145 | } else { | ||
146 | !0 | ||
147 | } | ||
148 | } | ||
149 | } | ||
150 | |||
151 | #[cfg(test)] | ||
152 | mod tests { | ||
153 | use super::*; | ||
154 | |||
155 | #[test] | ||
156 | fn path_prefix() { | ||
157 | let mut file_set = FileSetConfig::builder(); | ||
158 | file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo".into())]); | ||
159 | file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo/bar/baz".into())]); | ||
160 | let file_set = file_set.build(); | ||
161 | |||
162 | let mut vfs = Vfs::default(); | ||
163 | vfs.set_file_contents( | ||
164 | VfsPath::new_virtual_path("/foo/src/lib.rs".into()), | ||
165 | Some(Vec::new()), | ||
166 | ); | ||
167 | vfs.set_file_contents( | ||
168 | VfsPath::new_virtual_path("/foo/src/bar/baz/lib.rs".into()), | ||
169 | Some(Vec::new()), | ||
170 | ); | ||
171 | vfs.set_file_contents( | ||
172 | VfsPath::new_virtual_path("/foo/bar/baz/lib.rs".into()), | ||
173 | Some(Vec::new()), | ||
174 | ); | ||
175 | vfs.set_file_contents(VfsPath::new_virtual_path("/quux/lib.rs".into()), Some(Vec::new())); | ||
176 | |||
177 | let partition = file_set.partition(&vfs).into_iter().map(|it| it.len()).collect::<Vec<_>>(); | ||
178 | assert_eq!(partition, vec![2, 1, 1]); | ||
179 | } | ||
180 | |||
181 | #[test] | ||
182 | fn name_prefix() { | ||
183 | let mut file_set = FileSetConfig::builder(); | ||
184 | file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo".into())]); | ||
185 | file_set.add_file_set(vec![VfsPath::new_virtual_path("/foo-things".into())]); | ||
186 | let file_set = file_set.build(); | ||
187 | |||
188 | let mut vfs = Vfs::default(); | ||
189 | vfs.set_file_contents( | ||
190 | VfsPath::new_virtual_path("/foo/src/lib.rs".into()), | ||
191 | Some(Vec::new()), | ||
192 | ); | ||
193 | vfs.set_file_contents( | ||
194 | VfsPath::new_virtual_path("/foo-things/src/lib.rs".into()), | ||
195 | Some(Vec::new()), | ||
196 | ); | ||
197 | |||
198 | let partition = file_set.partition(&vfs).into_iter().map(|it| it.len()).collect::<Vec<_>>(); | ||
199 | assert_eq!(partition, vec![1, 1, 0]); | ||
107 | } | 200 | } |
108 | } | 201 | } |
diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs index 024e58018..cdf6f1fd0 100644 --- a/crates/vfs/src/lib.rs +++ b/crates/vfs/src/lib.rs | |||
@@ -70,7 +70,7 @@ impl ChangedFile { | |||
70 | } | 70 | } |
71 | } | 71 | } |
72 | 72 | ||
73 | #[derive(Eq, PartialEq)] | 73 | #[derive(Eq, PartialEq, Copy, Clone, Debug)] |
74 | pub enum ChangeKind { | 74 | pub enum ChangeKind { |
75 | Create, | 75 | Create, |
76 | Modify, | 76 | Modify, |
@@ -90,12 +90,12 @@ impl Vfs { | |||
90 | pub fn file_contents(&self, file_id: FileId) -> &[u8] { | 90 | pub fn file_contents(&self, file_id: FileId) -> &[u8] { |
91 | self.get(file_id).as_deref().unwrap() | 91 | self.get(file_id).as_deref().unwrap() |
92 | } | 92 | } |
93 | pub fn iter(&self) -> impl Iterator<Item = (FileId, VfsPath)> + '_ { | 93 | pub fn iter(&self) -> impl Iterator<Item = (FileId, &VfsPath)> + '_ { |
94 | (0..self.data.len()) | 94 | (0..self.data.len()) |
95 | .map(|it| FileId(it as u32)) | 95 | .map(|it| FileId(it as u32)) |
96 | .filter(move |&file_id| self.get(file_id).is_some()) | 96 | .filter(move |&file_id| self.get(file_id).is_some()) |
97 | .map(move |file_id| { | 97 | .map(move |file_id| { |
98 | let path = self.interner.lookup(file_id).clone(); | 98 | let path = self.interner.lookup(file_id); |
99 | (file_id, path) | 99 | (file_id, path) |
100 | }) | 100 | }) |
101 | } | 101 | } |
diff --git a/crates/vfs/src/loader.rs b/crates/vfs/src/loader.rs index 6de2e5b3f..40cf96020 100644 --- a/crates/vfs/src/loader.rs +++ b/crates/vfs/src/loader.rs | |||
@@ -3,10 +3,25 @@ use std::fmt; | |||
3 | 3 | ||
4 | use paths::{AbsPath, AbsPathBuf}; | 4 | use paths::{AbsPath, AbsPathBuf}; |
5 | 5 | ||
6 | #[derive(Debug)] | 6 | #[derive(Debug, Clone)] |
7 | pub enum Entry { | 7 | pub enum Entry { |
8 | Files(Vec<AbsPathBuf>), | 8 | Files(Vec<AbsPathBuf>), |
9 | Directory { path: AbsPathBuf, include: Vec<String> }, | 9 | Directories(Directories), |
10 | } | ||
11 | |||
12 | /// Specifies a set of files on the file system. | ||
13 | /// | ||
14 | /// A file is included if: | ||
15 | /// * it has included extension | ||
16 | /// * it is under an `include` path | ||
17 | /// * it is not under `exclude` path | ||
18 | /// | ||
19 | /// If many include/exclude paths match, the longest one wins. | ||
20 | #[derive(Debug, Clone, Default)] | ||
21 | pub struct Directories { | ||
22 | pub extensions: Vec<String>, | ||
23 | pub include: Vec<AbsPathBuf>, | ||
24 | pub exclude: Vec<AbsPathBuf>, | ||
10 | } | 25 | } |
11 | 26 | ||
12 | #[derive(Debug)] | 27 | #[derive(Debug)] |
@@ -33,21 +48,66 @@ pub trait Handle: fmt::Debug { | |||
33 | 48 | ||
34 | impl Entry { | 49 | impl Entry { |
35 | pub fn rs_files_recursively(base: AbsPathBuf) -> Entry { | 50 | pub fn rs_files_recursively(base: AbsPathBuf) -> Entry { |
36 | Entry::Directory { path: base, include: globs(&["*.rs", "!/.git/"]) } | 51 | Entry::Directories(dirs(base, &[".git"])) |
37 | } | 52 | } |
38 | pub fn local_cargo_package(base: AbsPathBuf) -> Entry { | 53 | pub fn local_cargo_package(base: AbsPathBuf) -> Entry { |
39 | Entry::Directory { path: base, include: globs(&["*.rs", "!/target/", "!/.git/"]) } | 54 | Entry::Directories(dirs(base, &[".git", "target"])) |
40 | } | 55 | } |
41 | pub fn cargo_package_dependency(base: AbsPathBuf) -> Entry { | 56 | pub fn cargo_package_dependency(base: AbsPathBuf) -> Entry { |
42 | Entry::Directory { | 57 | Entry::Directories(dirs(base, &[".git", "/tests", "/examples", "/benches"])) |
43 | path: base, | 58 | } |
44 | include: globs(&["*.rs", "!/tests/", "!/examples/", "!/benches/", "!/.git/"]), | 59 | |
60 | pub fn contains_file(&self, path: &AbsPath) -> bool { | ||
61 | match self { | ||
62 | Entry::Files(files) => files.iter().any(|it| it == path), | ||
63 | Entry::Directories(dirs) => dirs.contains_file(path), | ||
64 | } | ||
65 | } | ||
66 | pub fn contains_dir(&self, path: &AbsPath) -> bool { | ||
67 | match self { | ||
68 | Entry::Files(_) => false, | ||
69 | Entry::Directories(dirs) => dirs.contains_dir(path), | ||
70 | } | ||
71 | } | ||
72 | } | ||
73 | |||
74 | impl Directories { | ||
75 | pub fn contains_file(&self, path: &AbsPath) -> bool { | ||
76 | let ext = path.extension().unwrap_or_default(); | ||
77 | if self.extensions.iter().all(|it| it.as_str() != ext) { | ||
78 | return false; | ||
79 | } | ||
80 | self.includes_path(path) | ||
81 | } | ||
82 | pub fn contains_dir(&self, path: &AbsPath) -> bool { | ||
83 | self.includes_path(path) | ||
84 | } | ||
85 | fn includes_path(&self, path: &AbsPath) -> bool { | ||
86 | let mut include: Option<&AbsPathBuf> = None; | ||
87 | for incl in &self.include { | ||
88 | if path.starts_with(incl) { | ||
89 | include = Some(match include { | ||
90 | Some(prev) if prev.starts_with(incl) => prev, | ||
91 | _ => incl, | ||
92 | }) | ||
93 | } | ||
94 | } | ||
95 | let include = match include { | ||
96 | Some(it) => it, | ||
97 | None => return false, | ||
98 | }; | ||
99 | for excl in &self.exclude { | ||
100 | if path.starts_with(excl) && excl.starts_with(include) { | ||
101 | return false; | ||
102 | } | ||
45 | } | 103 | } |
104 | true | ||
46 | } | 105 | } |
47 | } | 106 | } |
48 | 107 | ||
49 | fn globs(globs: &[&str]) -> Vec<String> { | 108 | fn dirs(base: AbsPathBuf, exclude: &[&str]) -> Directories { |
50 | globs.iter().map(|it| it.to_string()).collect() | 109 | let exclude = exclude.iter().map(|it| base.join(it)).collect::<Vec<_>>(); |
110 | Directories { extensions: vec!["rs".to_string()], include: vec![base], exclude } | ||
51 | } | 111 | } |
52 | 112 | ||
53 | impl fmt::Debug for Message { | 113 | impl fmt::Debug for Message { |
diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs index dc3031ada..04a42264e 100644 --- a/crates/vfs/src/vfs_path.rs +++ b/crates/vfs/src/vfs_path.rs | |||
@@ -48,6 +48,37 @@ impl VfsPath { | |||
48 | (VfsPathRepr::VirtualPath(_), _) => false, | 48 | (VfsPathRepr::VirtualPath(_), _) => false, |
49 | } | 49 | } |
50 | } | 50 | } |
51 | |||
52 | // Don't make this `pub` | ||
53 | pub(crate) fn encode(&self, buf: &mut Vec<u8>) { | ||
54 | let tag = match &self.0 { | ||
55 | VfsPathRepr::PathBuf(_) => 0, | ||
56 | VfsPathRepr::VirtualPath(_) => 1, | ||
57 | }; | ||
58 | buf.push(tag); | ||
59 | match &self.0 { | ||
60 | VfsPathRepr::PathBuf(it) => { | ||
61 | let path: &std::ffi::OsStr = it.as_os_str(); | ||
62 | #[cfg(windows)] | ||
63 | { | ||
64 | use std::os::windows::ffi::OsStrExt; | ||
65 | for wchar in path.encode_wide() { | ||
66 | buf.extend(wchar.to_le_bytes().iter().copied()); | ||
67 | } | ||
68 | } | ||
69 | #[cfg(unix)] | ||
70 | { | ||
71 | use std::os::unix::ffi::OsStrExt; | ||
72 | buf.extend(path.as_bytes()); | ||
73 | } | ||
74 | #[cfg(not(any(windows, unix)))] | ||
75 | { | ||
76 | buf.extend(path.to_string_lossy().as_bytes()); | ||
77 | } | ||
78 | } | ||
79 | VfsPathRepr::VirtualPath(VirtualPath(s)) => buf.extend(s.as_bytes()), | ||
80 | } | ||
81 | } | ||
51 | } | 82 | } |
52 | 83 | ||
53 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] | 84 | #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] |