aboutsummaryrefslogtreecommitdiff
path: root/crates/vfs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/vfs')
-rw-r--r--crates/vfs/Cargo.toml5
-rw-r--r--crates/vfs/src/file_set.rs135
-rw-r--r--crates/vfs/src/lib.rs6
-rw-r--r--crates/vfs/src/loader.rs78
-rw-r--r--crates/vfs/src/vfs_path.rs31
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"
3version = "0.1.0" 3version = "0.1.0"
4authors = ["rust-analyzer developers"] 4authors = ["rust-analyzer developers"]
5edition = "2018" 5edition = "2018"
6license = "MIT OR Apache-2.0"
7
8[lib]
9doctest = false
6 10
7[dependencies] 11[dependencies]
8rustc-hash = "1.0" 12rustc-hash = "1.0"
13fst = "0.4"
9 14
10paths = { path = "../paths" } 15paths = { 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`.
5use std::{fmt, iter}; 5use std::fmt;
6 6
7use fst::{IntoStreamer, Streamer};
7use rustc_hash::FxHashMap; 8use rustc_hash::FxHashMap;
8 9
9use crate::{FileId, Vfs, VfsPath}; 10use crate::{FileId, Vfs, VfsPath};
@@ -15,6 +16,9 @@ pub struct FileSet {
15} 16}
16 17
17impl FileSet { 18impl 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)]
41pub struct FileSetConfig { 45pub 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
46impl Default for FileSetConfig { 50impl 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
121struct PrefixOf<'a> {
122 prefix_of: &'a [u8],
123}
124
125impl<'a> PrefixOf<'a> {
126 fn new(prefix_of: &'a [u8]) -> Self {
127 Self { prefix_of }
128 }
129}
130
131impl 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)]
152mod 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)]
74pub enum ChangeKind { 74pub 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
4use paths::{AbsPath, AbsPathBuf}; 4use paths::{AbsPath, AbsPathBuf};
5 5
6#[derive(Debug)] 6#[derive(Debug, Clone)]
7pub enum Entry { 7pub 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)]
21pub 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
34impl Entry { 49impl 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
74impl 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
49fn globs(globs: &[&str]) -> Vec<String> { 108fn 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
53impl fmt::Debug for Message { 113impl 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)]