aboutsummaryrefslogtreecommitdiff
path: root/crates
diff options
context:
space:
mode:
Diffstat (limited to 'crates')
-rw-r--r--crates/rust-analyzer/Cargo.toml1
-rw-r--r--crates/vfs-notify/Cargo.toml1
-rw-r--r--crates/vfs-notify/src/include.rs42
-rw-r--r--crates/vfs-notify/src/lib.rs104
-rw-r--r--crates/vfs/src/loader.rs82
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"
17anyhow = "1.0.26" 17anyhow = "1.0.26"
18crossbeam-channel = "0.4.0" 18crossbeam-channel = "0.4.0"
19env_logger = { version = "0.7.1", default-features = false } 19env_logger = { version = "0.7.1", default-features = false }
20globset = "0.4.4"
21itertools = "0.9.0" 20itertools = "0.9.0"
22jod-thread = "0.1.0" 21jod-thread = "0.1.0"
23log = "0.4.8" 22log = "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"
13rustc-hash = "1.0" 13rustc-hash = "1.0"
14jod-thread = "0.1.0" 14jod-thread = "0.1.0"
15walkdir = "2.3.1" 15walkdir = "2.3.1"
16globset = "0.4.5"
17crossbeam-channel = "0.4.0" 16crossbeam-channel = "0.4.0"
18notify = "5.0.0-pre.3" 17notify = "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`.
2use std::convert::TryFrom;
3
4use globset::{Glob, GlobSet, GlobSetBuilder};
5use 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)]
14pub(crate) struct Include {
15 include_files: GlobSet,
16 exclude_dirs: Vec<RelPathBuf>,
17}
18
19impl 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.
9mod include; 9use std::convert::TryFrom;
10
11use std::convert::{TryFrom, TryInto};
12 10
13use crossbeam_channel::{never, select, unbounded, Receiver, Sender}; 11use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
14use notify::{RecommendedWatcher, RecursiveMode, Watcher}; 12use notify::{RecommendedWatcher, RecursiveMode, Watcher};
@@ -16,8 +14,6 @@ use paths::{AbsPath, AbsPathBuf};
16use vfs::loader; 14use vfs::loader;
17use walkdir::WalkDir; 15use walkdir::WalkDir;
18 16
19use crate::include::Include;
20
21#[derive(Debug)] 17#[derive(Debug)]
22pub struct NotifyHandle { 18pub 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
54struct NotifyActor { 50struct 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
67impl NotifyActor { 63impl 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
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)]
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,70 @@ 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 = 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
49fn globs(globs: &[&str]) -> Vec<String> { 112fn 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
53impl fmt::Debug for Message { 117impl fmt::Debug for Message {