diff options
Diffstat (limited to 'crates')
-rw-r--r-- | crates/rust-analyzer/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/vfs-notify/Cargo.toml | 1 | ||||
-rw-r--r-- | crates/vfs-notify/src/include.rs | 42 | ||||
-rw-r--r-- | crates/vfs-notify/src/lib.rs | 104 | ||||
-rw-r--r-- | crates/vfs/src/loader.rs | 82 |
5 files changed, 120 insertions, 110 deletions
diff --git a/crates/rust-analyzer/Cargo.toml b/crates/rust-analyzer/Cargo.toml index 3ed2a689e..5eb2d0bb7 100644 --- a/crates/rust-analyzer/Cargo.toml +++ b/crates/rust-analyzer/Cargo.toml | |||
@@ -17,7 +17,6 @@ path = "src/bin/main.rs" | |||
17 | anyhow = "1.0.26" | 17 | anyhow = "1.0.26" |
18 | crossbeam-channel = "0.4.0" | 18 | crossbeam-channel = "0.4.0" |
19 | env_logger = { version = "0.7.1", default-features = false } | 19 | env_logger = { version = "0.7.1", default-features = false } |
20 | globset = "0.4.4" | ||
21 | itertools = "0.9.0" | 20 | itertools = "0.9.0" |
22 | jod-thread = "0.1.0" | 21 | jod-thread = "0.1.0" |
23 | log = "0.4.8" | 22 | log = "0.4.8" |
diff --git a/crates/vfs-notify/Cargo.toml b/crates/vfs-notify/Cargo.toml index 95c56ffa6..fce7bae3a 100644 --- a/crates/vfs-notify/Cargo.toml +++ b/crates/vfs-notify/Cargo.toml | |||
@@ -13,7 +13,6 @@ log = "0.4.8" | |||
13 | rustc-hash = "1.0" | 13 | rustc-hash = "1.0" |
14 | jod-thread = "0.1.0" | 14 | jod-thread = "0.1.0" |
15 | walkdir = "2.3.1" | 15 | walkdir = "2.3.1" |
16 | globset = "0.4.5" | ||
17 | crossbeam-channel = "0.4.0" | 16 | crossbeam-channel = "0.4.0" |
18 | notify = "5.0.0-pre.3" | 17 | notify = "5.0.0-pre.3" |
19 | 18 | ||
diff --git a/crates/vfs-notify/src/include.rs b/crates/vfs-notify/src/include.rs deleted file mode 100644 index 59da3d77a..000000000 --- a/crates/vfs-notify/src/include.rs +++ /dev/null | |||
@@ -1,42 +0,0 @@ | |||
1 | //! See `Include`. | ||
2 | use std::convert::TryFrom; | ||
3 | |||
4 | use globset::{Glob, GlobSet, GlobSetBuilder}; | ||
5 | use paths::{RelPath, RelPathBuf}; | ||
6 | |||
7 | /// `Include` is the opposite of .gitignore. | ||
8 | /// | ||
9 | /// It describes the set of files inside some directory. | ||
10 | /// | ||
11 | /// The current implementation is very limited, it allows including file globs | ||
12 | /// and recursively excluding directories. | ||
13 | #[derive(Debug, Clone)] | ||
14 | pub(crate) struct Include { | ||
15 | include_files: GlobSet, | ||
16 | exclude_dirs: Vec<RelPathBuf>, | ||
17 | } | ||
18 | |||
19 | impl Include { | ||
20 | pub(crate) fn new(include: Vec<String>) -> Include { | ||
21 | let mut include_files = GlobSetBuilder::new(); | ||
22 | let mut exclude_dirs = Vec::new(); | ||
23 | |||
24 | for glob in include { | ||
25 | if glob.starts_with("!/") { | ||
26 | if let Ok(path) = RelPathBuf::try_from(&glob["!/".len()..]) { | ||
27 | exclude_dirs.push(path) | ||
28 | } | ||
29 | } else { | ||
30 | include_files.add(Glob::new(&glob).unwrap()); | ||
31 | } | ||
32 | } | ||
33 | let include_files = include_files.build().unwrap(); | ||
34 | Include { include_files, exclude_dirs } | ||
35 | } | ||
36 | pub(crate) fn include_file(&self, path: &RelPath) -> bool { | ||
37 | self.include_files.is_match(path) | ||
38 | } | ||
39 | pub(crate) fn exclude_dir(&self, path: &RelPath) -> bool { | ||
40 | self.exclude_dirs.iter().any(|excluded| path.starts_with(excluded)) | ||
41 | } | ||
42 | } | ||
diff --git a/crates/vfs-notify/src/lib.rs b/crates/vfs-notify/src/lib.rs index 6aaa53f63..e1e36612a 100644 --- a/crates/vfs-notify/src/lib.rs +++ b/crates/vfs-notify/src/lib.rs | |||
@@ -6,9 +6,7 @@ | |||
6 | //! | 6 | //! |
7 | //! Hopefully, one day a reliable file watching/walking crate appears on | 7 | //! Hopefully, one day a reliable file watching/walking crate appears on |
8 | //! crates.io, and we can reduce this to trivial glue code. | 8 | //! crates.io, and we can reduce this to trivial glue code. |
9 | mod include; | 9 | use std::convert::TryFrom; |
10 | |||
11 | use std::convert::{TryFrom, TryInto}; | ||
12 | 10 | ||
13 | use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; | 11 | use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; |
14 | use notify::{RecommendedWatcher, RecursiveMode, Watcher}; | 12 | use notify::{RecommendedWatcher, RecursiveMode, Watcher}; |
@@ -16,8 +14,6 @@ use paths::{AbsPath, AbsPathBuf}; | |||
16 | use vfs::loader; | 14 | use vfs::loader; |
17 | use walkdir::WalkDir; | 15 | use walkdir::WalkDir; |
18 | 16 | ||
19 | use crate::include::Include; | ||
20 | |||
21 | #[derive(Debug)] | 17 | #[derive(Debug)] |
22 | pub struct NotifyHandle { | 18 | pub struct NotifyHandle { |
23 | // Relative order of fields below is significant. | 19 | // Relative order of fields below is significant. |
@@ -53,7 +49,7 @@ type NotifyEvent = notify::Result<notify::Event>; | |||
53 | 49 | ||
54 | struct NotifyActor { | 50 | struct NotifyActor { |
55 | sender: loader::Sender, | 51 | sender: loader::Sender, |
56 | config: Vec<(AbsPathBuf, Include, bool)>, | 52 | watched_entries: Vec<loader::Entry>, |
57 | // Drop order is significant. | 53 | // Drop order is significant. |
58 | watcher: Option<(RecommendedWatcher, Receiver<NotifyEvent>)>, | 54 | watcher: Option<(RecommendedWatcher, Receiver<NotifyEvent>)>, |
59 | } | 55 | } |
@@ -66,7 +62,7 @@ enum Event { | |||
66 | 62 | ||
67 | impl NotifyActor { | 63 | impl NotifyActor { |
68 | fn new(sender: loader::Sender) -> NotifyActor { | 64 | fn new(sender: loader::Sender) -> NotifyActor { |
69 | NotifyActor { sender, config: Vec::new(), watcher: None } | 65 | NotifyActor { sender, watched_entries: Vec::new(), watcher: None } |
70 | } | 66 | } |
71 | fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> { | 67 | fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> { |
72 | let watcher_receiver = self.watcher.as_ref().map(|(_, receiver)| receiver); | 68 | let watcher_receiver = self.watcher.as_ref().map(|(_, receiver)| receiver); |
@@ -93,15 +89,17 @@ impl NotifyActor { | |||
93 | let n_total = config.load.len(); | 89 | let n_total = config.load.len(); |
94 | self.send(loader::Message::Progress { n_total, n_done: 0 }); | 90 | self.send(loader::Message::Progress { n_total, n_done: 0 }); |
95 | 91 | ||
96 | self.config.clear(); | 92 | self.watched_entries.clear(); |
97 | 93 | ||
98 | for (i, entry) in config.load.into_iter().enumerate() { | 94 | for (i, entry) in config.load.into_iter().enumerate() { |
99 | let watch = config.watch.contains(&i); | 95 | let watch = config.watch.contains(&i); |
96 | if watch { | ||
97 | self.watched_entries.push(entry.clone()) | ||
98 | } | ||
100 | let files = self.load_entry(entry, watch); | 99 | let files = self.load_entry(entry, watch); |
101 | self.send(loader::Message::Loaded { files }); | 100 | self.send(loader::Message::Loaded { files }); |
102 | self.send(loader::Message::Progress { n_total, n_done: i + 1 }); | 101 | self.send(loader::Message::Progress { n_total, n_done: i + 1 }); |
103 | } | 102 | } |
104 | self.config.sort_by(|x, y| x.0.cmp(&y.0)); | ||
105 | } | 103 | } |
106 | Message::Invalidate(path) => { | 104 | Message::Invalidate(path) => { |
107 | let contents = read(path.as_path()); | 105 | let contents = read(path.as_path()); |
@@ -116,34 +114,27 @@ impl NotifyActor { | |||
116 | .into_iter() | 114 | .into_iter() |
117 | .map(|path| AbsPathBuf::try_from(path).unwrap()) | 115 | .map(|path| AbsPathBuf::try_from(path).unwrap()) |
118 | .filter_map(|path| { | 116 | .filter_map(|path| { |
119 | let is_dir = path.is_dir(); | 117 | if path.is_dir() |
120 | let is_file = path.is_file(); | 118 | && self |
121 | 119 | .watched_entries | |
122 | let config_idx = | 120 | .iter() |
123 | match self.config.binary_search_by(|it| it.0.cmp(&path)) { | 121 | .any(|entry| entry.contains_dir(&path)) |
124 | Ok(it) => it, | 122 | { |
125 | Err(it) => it.saturating_sub(1), | 123 | self.watch(path); |
126 | }; | 124 | return None; |
127 | let include = self.config.get(config_idx).and_then(|it| { | ||
128 | let rel_path = path.strip_prefix(&it.0)?; | ||
129 | Some((rel_path, &it.1)) | ||
130 | }); | ||
131 | |||
132 | if let Some((rel_path, include)) = include { | ||
133 | if is_dir && include.exclude_dir(&rel_path) | ||
134 | || is_file && !include.include_file(&rel_path) | ||
135 | { | ||
136 | return None; | ||
137 | } | ||
138 | } | 125 | } |
139 | 126 | ||
140 | if is_dir { | 127 | if !path.is_file() { |
141 | self.watch(path); | ||
142 | return None; | 128 | return None; |
143 | } | 129 | } |
144 | if !is_file { | 130 | if !self |
131 | .watched_entries | ||
132 | .iter() | ||
133 | .any(|entry| entry.contains_file(&path)) | ||
134 | { | ||
145 | return None; | 135 | return None; |
146 | } | 136 | } |
137 | |||
147 | let contents = read(&path); | 138 | let contents = read(&path); |
148 | Some((path, contents)) | 139 | Some((path, contents)) |
149 | }) | 140 | }) |
@@ -170,43 +161,42 @@ impl NotifyActor { | |||
170 | (file, contents) | 161 | (file, contents) |
171 | }) | 162 | }) |
172 | .collect::<Vec<_>>(), | 163 | .collect::<Vec<_>>(), |
173 | loader::Entry::Directory { path, include } => { | 164 | loader::Entry::Directories(dirs) => { |
174 | let include = Include::new(include); | 165 | let mut res = Vec::new(); |
175 | self.config.push((path.clone(), include.clone(), watch)); | 166 | |
176 | 167 | for root in dirs.include.iter() { | |
177 | let files = WalkDir::new(&path) | 168 | let walkdir = WalkDir::new(root).into_iter().filter_entry(|entry| { |
178 | .into_iter() | 169 | if !entry.file_type().is_dir() { |
179 | .filter_entry(|entry| { | 170 | return true; |
180 | let abs_path: &AbsPath = entry.path().try_into().unwrap(); | ||
181 | match abs_path.strip_prefix(&path) { | ||
182 | Some(rel_path) => { | ||
183 | !(entry.file_type().is_dir() && include.exclude_dir(rel_path)) | ||
184 | } | ||
185 | None => false, | ||
186 | } | 171 | } |
187 | }) | 172 | let path = AbsPath::assert(entry.path()); |
188 | .filter_map(|entry| entry.ok()) | 173 | root == path |
189 | .filter_map(|entry| { | 174 | || dirs.exclude.iter().chain(&dirs.include).all(|it| it != path) |
175 | }); | ||
176 | |||
177 | let files = walkdir.filter_map(|it| it.ok()).filter_map(|entry| { | ||
190 | let is_dir = entry.file_type().is_dir(); | 178 | let is_dir = entry.file_type().is_dir(); |
191 | let is_file = entry.file_type().is_file(); | 179 | let is_file = entry.file_type().is_file(); |
192 | let abs_path = AbsPathBuf::try_from(entry.into_path()).unwrap(); | 180 | let abs_path = AbsPathBuf::assert(entry.into_path()); |
193 | if is_dir && watch { | 181 | if is_dir && watch { |
194 | self.watch(abs_path.clone()); | 182 | self.watch(abs_path.clone()); |
195 | } | 183 | } |
196 | let rel_path = abs_path.strip_prefix(&path)?; | 184 | if !is_file { |
197 | if is_file && include.include_file(&rel_path) { | 185 | return None; |
198 | Some(abs_path) | 186 | } |
199 | } else { | 187 | let ext = abs_path.extension().unwrap_or_default(); |
200 | None | 188 | if dirs.extensions.iter().all(|it| it.as_str() != ext) { |
189 | return None; | ||
201 | } | 190 | } |
191 | Some(abs_path) | ||
202 | }); | 192 | }); |
203 | 193 | ||
204 | files | 194 | res.extend(files.map(|file| { |
205 | .map(|file| { | ||
206 | let contents = read(file.as_path()); | 195 | let contents = read(file.as_path()); |
207 | (file, contents) | 196 | (file, contents) |
208 | }) | 197 | })); |
209 | .collect() | 198 | } |
199 | res | ||
210 | } | 200 | } |
211 | } | 201 | } |
212 | } | 202 | } |
diff --git a/crates/vfs/src/loader.rs b/crates/vfs/src/loader.rs index 6de2e5b3f..9c6e4b6a7 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)] | ||
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,70 @@ 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 = None; | ||
87 | for incl in &self.include { | ||
88 | if is_prefix(incl, path) { | ||
89 | include = Some(match include { | ||
90 | Some(prev) if is_prefix(incl, prev) => 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 is_prefix(excl, path) && is_prefix(include, excl) { | ||
101 | return false; | ||
102 | } | ||
103 | } | ||
104 | return true; | ||
105 | |||
106 | fn is_prefix(short: &AbsPath, long: &AbsPath) -> bool { | ||
107 | long.strip_prefix(short).is_some() | ||
45 | } | 108 | } |
46 | } | 109 | } |
47 | } | 110 | } |
48 | 111 | ||
49 | fn globs(globs: &[&str]) -> Vec<String> { | 112 | fn dirs(base: AbsPathBuf, exclude: &[&str]) -> Directories { |
50 | globs.iter().map(|it| it.to_string()).collect() | 113 | let exclude = exclude.iter().map(|it| base.join(it)).collect::<Vec<_>>(); |
114 | Directories { extensions: vec!["rs".to_string()], include: vec![base], exclude } | ||
51 | } | 115 | } |
52 | 116 | ||
53 | impl fmt::Debug for Message { | 117 | impl fmt::Debug for Message { |