mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-11 20:58:54 +00:00
Simplify exclusion logic
This commit is contained in:
parent
fd6717799c
commit
46ac9ff5e3
7 changed files with 120 additions and 141 deletions
30
Cargo.lock
generated
30
Cargo.lock
generated
|
@ -103,15 +103,6 @@ version = "1.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "bstr"
|
||||
version = "0.2.13"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "31accafdb70df7871592c058eca3985b71104e15ac32f64706022c58867da931"
|
||||
dependencies = [
|
||||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.3.4"
|
||||
|
@ -403,12 +394,6 @@ dependencies = [
|
|||
"serde_json",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "fnv"
|
||||
version = "1.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||
|
||||
[[package]]
|
||||
name = "fs_extra"
|
||||
version = "1.1.0"
|
||||
|
@ -473,19 +458,6 @@ version = "0.22.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724"
|
||||
|
||||
[[package]]
|
||||
name = "globset"
|
||||
version = "0.4.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7ad1da430bd7281dde2576f44c84cc3f0f7b475e7202cd503042dff01a8c8120"
|
||||
dependencies = [
|
||||
"aho-corasick",
|
||||
"bstr",
|
||||
"fnv",
|
||||
"log",
|
||||
"regex",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "goblin"
|
||||
version = "0.2.3"
|
||||
|
@ -1494,7 +1466,6 @@ dependencies = [
|
|||
"env_logger",
|
||||
"expect",
|
||||
"flycheck",
|
||||
"globset",
|
||||
"itertools",
|
||||
"jod-thread",
|
||||
"log",
|
||||
|
@ -2003,7 +1974,6 @@ name = "vfs-notify"
|
|||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"crossbeam-channel",
|
||||
"globset",
|
||||
"jod-thread",
|
||||
"log",
|
||||
"notify",
|
||||
|
|
|
@ -17,7 +17,6 @@ path = "src/bin/main.rs"
|
|||
anyhow = "1.0.26"
|
||||
crossbeam-channel = "0.4.0"
|
||||
env_logger = { version = "0.7.1", default-features = false }
|
||||
globset = "0.4.4"
|
||||
itertools = "0.9.0"
|
||||
jod-thread = "0.1.0"
|
||||
log = "0.4.8"
|
||||
|
|
|
@ -13,7 +13,6 @@ log = "0.4.8"
|
|||
rustc-hash = "1.0"
|
||||
jod-thread = "0.1.0"
|
||||
walkdir = "2.3.1"
|
||||
globset = "0.4.5"
|
||||
crossbeam-channel = "0.4.0"
|
||||
notify = "5.0.0-pre.3"
|
||||
|
||||
|
|
|
@ -1,42 +0,0 @@
|
|||
//! See `Include`.
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use globset::{Glob, GlobSet, GlobSetBuilder};
|
||||
use paths::{RelPath, RelPathBuf};
|
||||
|
||||
/// `Include` is the opposite of .gitignore.
|
||||
///
|
||||
/// It describes the set of files inside some directory.
|
||||
///
|
||||
/// The current implementation is very limited, it allows including file globs
|
||||
/// and recursively excluding directories.
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct Include {
|
||||
include_files: GlobSet,
|
||||
exclude_dirs: Vec<RelPathBuf>,
|
||||
}
|
||||
|
||||
impl Include {
|
||||
pub(crate) fn new(include: Vec<String>) -> Include {
|
||||
let mut include_files = GlobSetBuilder::new();
|
||||
let mut exclude_dirs = Vec::new();
|
||||
|
||||
for glob in include {
|
||||
if glob.starts_with("!/") {
|
||||
if let Ok(path) = RelPathBuf::try_from(&glob["!/".len()..]) {
|
||||
exclude_dirs.push(path)
|
||||
}
|
||||
} else {
|
||||
include_files.add(Glob::new(&glob).unwrap());
|
||||
}
|
||||
}
|
||||
let include_files = include_files.build().unwrap();
|
||||
Include { include_files, exclude_dirs }
|
||||
}
|
||||
pub(crate) fn include_file(&self, path: &RelPath) -> bool {
|
||||
self.include_files.is_match(path)
|
||||
}
|
||||
pub(crate) fn exclude_dir(&self, path: &RelPath) -> bool {
|
||||
self.exclude_dirs.iter().any(|excluded| path.starts_with(excluded))
|
||||
}
|
||||
}
|
|
@ -6,9 +6,7 @@
|
|||
//!
|
||||
//! Hopefully, one day a reliable file watching/walking crate appears on
|
||||
//! crates.io, and we can reduce this to trivial glue code.
|
||||
mod include;
|
||||
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use crossbeam_channel::{never, select, unbounded, Receiver, Sender};
|
||||
use notify::{RecommendedWatcher, RecursiveMode, Watcher};
|
||||
|
@ -16,8 +14,6 @@ use paths::{AbsPath, AbsPathBuf};
|
|||
use vfs::loader;
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::include::Include;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct NotifyHandle {
|
||||
// Relative order of fields below is significant.
|
||||
|
@ -53,7 +49,7 @@ type NotifyEvent = notify::Result<notify::Event>;
|
|||
|
||||
struct NotifyActor {
|
||||
sender: loader::Sender,
|
||||
config: Vec<(AbsPathBuf, Include, bool)>,
|
||||
watched_entries: Vec<loader::Entry>,
|
||||
// Drop order is significant.
|
||||
watcher: Option<(RecommendedWatcher, Receiver<NotifyEvent>)>,
|
||||
}
|
||||
|
@ -66,7 +62,7 @@ enum Event {
|
|||
|
||||
impl NotifyActor {
|
||||
fn new(sender: loader::Sender) -> NotifyActor {
|
||||
NotifyActor { sender, config: Vec::new(), watcher: None }
|
||||
NotifyActor { sender, watched_entries: Vec::new(), watcher: None }
|
||||
}
|
||||
fn next_event(&self, receiver: &Receiver<Message>) -> Option<Event> {
|
||||
let watcher_receiver = self.watcher.as_ref().map(|(_, receiver)| receiver);
|
||||
|
@ -93,15 +89,17 @@ impl NotifyActor {
|
|||
let n_total = config.load.len();
|
||||
self.send(loader::Message::Progress { n_total, n_done: 0 });
|
||||
|
||||
self.config.clear();
|
||||
self.watched_entries.clear();
|
||||
|
||||
for (i, entry) in config.load.into_iter().enumerate() {
|
||||
let watch = config.watch.contains(&i);
|
||||
if watch {
|
||||
self.watched_entries.push(entry.clone())
|
||||
}
|
||||
let files = self.load_entry(entry, watch);
|
||||
self.send(loader::Message::Loaded { files });
|
||||
self.send(loader::Message::Progress { n_total, n_done: i + 1 });
|
||||
}
|
||||
self.config.sort_by(|x, y| x.0.cmp(&y.0));
|
||||
}
|
||||
Message::Invalidate(path) => {
|
||||
let contents = read(path.as_path());
|
||||
|
@ -116,34 +114,27 @@ impl NotifyActor {
|
|||
.into_iter()
|
||||
.map(|path| AbsPathBuf::try_from(path).unwrap())
|
||||
.filter_map(|path| {
|
||||
let is_dir = path.is_dir();
|
||||
let is_file = path.is_file();
|
||||
|
||||
let config_idx =
|
||||
match self.config.binary_search_by(|it| it.0.cmp(&path)) {
|
||||
Ok(it) => it,
|
||||
Err(it) => it.saturating_sub(1),
|
||||
};
|
||||
let include = self.config.get(config_idx).and_then(|it| {
|
||||
let rel_path = path.strip_prefix(&it.0)?;
|
||||
Some((rel_path, &it.1))
|
||||
});
|
||||
|
||||
if let Some((rel_path, include)) = include {
|
||||
if is_dir && include.exclude_dir(&rel_path)
|
||||
|| is_file && !include.include_file(&rel_path)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
if is_dir {
|
||||
if path.is_dir()
|
||||
&& self
|
||||
.watched_entries
|
||||
.iter()
|
||||
.any(|entry| entry.contains_dir(&path))
|
||||
{
|
||||
self.watch(path);
|
||||
return None;
|
||||
}
|
||||
if !is_file {
|
||||
|
||||
if !path.is_file() {
|
||||
return None;
|
||||
}
|
||||
if !self
|
||||
.watched_entries
|
||||
.iter()
|
||||
.any(|entry| entry.contains_file(&path))
|
||||
{
|
||||
return None;
|
||||
}
|
||||
|
||||
let contents = read(&path);
|
||||
Some((path, contents))
|
||||
})
|
||||
|
@ -170,43 +161,42 @@ impl NotifyActor {
|
|||
(file, contents)
|
||||
})
|
||||
.collect::<Vec<_>>(),
|
||||
loader::Entry::Directory { path, include } => {
|
||||
let include = Include::new(include);
|
||||
self.config.push((path.clone(), include.clone(), watch));
|
||||
loader::Entry::Directories(dirs) => {
|
||||
let mut res = Vec::new();
|
||||
|
||||
let files = WalkDir::new(&path)
|
||||
.into_iter()
|
||||
.filter_entry(|entry| {
|
||||
let abs_path: &AbsPath = entry.path().try_into().unwrap();
|
||||
match abs_path.strip_prefix(&path) {
|
||||
Some(rel_path) => {
|
||||
!(entry.file_type().is_dir() && include.exclude_dir(rel_path))
|
||||
}
|
||||
None => false,
|
||||
for root in dirs.include.iter() {
|
||||
let walkdir = WalkDir::new(root).into_iter().filter_entry(|entry| {
|
||||
if !entry.file_type().is_dir() {
|
||||
return true;
|
||||
}
|
||||
})
|
||||
.filter_map(|entry| entry.ok())
|
||||
.filter_map(|entry| {
|
||||
let path = AbsPath::assert(entry.path());
|
||||
root == path
|
||||
|| dirs.exclude.iter().chain(&dirs.include).all(|it| it != path)
|
||||
});
|
||||
|
||||
let files = walkdir.filter_map(|it| it.ok()).filter_map(|entry| {
|
||||
let is_dir = entry.file_type().is_dir();
|
||||
let is_file = entry.file_type().is_file();
|
||||
let abs_path = AbsPathBuf::try_from(entry.into_path()).unwrap();
|
||||
let abs_path = AbsPathBuf::assert(entry.into_path());
|
||||
if is_dir && watch {
|
||||
self.watch(abs_path.clone());
|
||||
}
|
||||
let rel_path = abs_path.strip_prefix(&path)?;
|
||||
if is_file && include.include_file(&rel_path) {
|
||||
Some(abs_path)
|
||||
} else {
|
||||
None
|
||||
if !is_file {
|
||||
return None;
|
||||
}
|
||||
let ext = abs_path.extension().unwrap_or_default();
|
||||
if dirs.extensions.iter().all(|it| it.as_str() != ext) {
|
||||
return None;
|
||||
}
|
||||
Some(abs_path)
|
||||
});
|
||||
|
||||
files
|
||||
.map(|file| {
|
||||
res.extend(files.map(|file| {
|
||||
let contents = read(file.as_path());
|
||||
(file, contents)
|
||||
})
|
||||
.collect()
|
||||
}));
|
||||
}
|
||||
res
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,10 +3,25 @@ use std::fmt;
|
|||
|
||||
use paths::{AbsPath, AbsPathBuf};
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Entry {
|
||||
Files(Vec<AbsPathBuf>),
|
||||
Directory { path: AbsPathBuf, include: Vec<String> },
|
||||
Directories(Directories),
|
||||
}
|
||||
|
||||
/// Specifies a set of files on the file system.
|
||||
///
|
||||
/// A file is included if:
|
||||
/// * it has included extension
|
||||
/// * it is under an `include` path
|
||||
/// * it is not under `exclude` path
|
||||
///
|
||||
/// If many include/exclude paths match, the longest one wins.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Directories {
|
||||
pub extensions: Vec<String>,
|
||||
pub include: Vec<AbsPathBuf>,
|
||||
pub exclude: Vec<AbsPathBuf>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
|
@ -33,21 +48,70 @@ pub trait Handle: fmt::Debug {
|
|||
|
||||
impl Entry {
|
||||
pub fn rs_files_recursively(base: AbsPathBuf) -> Entry {
|
||||
Entry::Directory { path: base, include: globs(&["*.rs", "!/.git/"]) }
|
||||
Entry::Directories(dirs(base, &[".git"]))
|
||||
}
|
||||
pub fn local_cargo_package(base: AbsPathBuf) -> Entry {
|
||||
Entry::Directory { path: base, include: globs(&["*.rs", "!/target/", "!/.git/"]) }
|
||||
Entry::Directories(dirs(base, &[".git", "target"]))
|
||||
}
|
||||
pub fn cargo_package_dependency(base: AbsPathBuf) -> Entry {
|
||||
Entry::Directory {
|
||||
path: base,
|
||||
include: globs(&["*.rs", "!/tests/", "!/examples/", "!/benches/", "!/.git/"]),
|
||||
Entry::Directories(dirs(base, &[".git", "/tests", "/examples", "/benches"]))
|
||||
}
|
||||
|
||||
pub fn contains_file(&self, path: &AbsPath) -> bool {
|
||||
match self {
|
||||
Entry::Files(files) => files.iter().any(|it| it == path),
|
||||
Entry::Directories(dirs) => dirs.contains_file(path),
|
||||
}
|
||||
}
|
||||
pub fn contains_dir(&self, path: &AbsPath) -> bool {
|
||||
match self {
|
||||
Entry::Files(_) => false,
|
||||
Entry::Directories(dirs) => dirs.contains_dir(path),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn globs(globs: &[&str]) -> Vec<String> {
|
||||
globs.iter().map(|it| it.to_string()).collect()
|
||||
impl Directories {
|
||||
pub fn contains_file(&self, path: &AbsPath) -> bool {
|
||||
let ext = path.extension().unwrap_or_default();
|
||||
if self.extensions.iter().all(|it| it.as_str() != ext) {
|
||||
return false;
|
||||
}
|
||||
self.includes_path(path)
|
||||
}
|
||||
pub fn contains_dir(&self, path: &AbsPath) -> bool {
|
||||
self.includes_path(path)
|
||||
}
|
||||
fn includes_path(&self, path: &AbsPath) -> bool {
|
||||
let mut include = None;
|
||||
for incl in &self.include {
|
||||
if is_prefix(incl, path) {
|
||||
include = Some(match include {
|
||||
Some(prev) if is_prefix(incl, prev) => prev,
|
||||
_ => incl,
|
||||
})
|
||||
}
|
||||
}
|
||||
let include = match include {
|
||||
Some(it) => it,
|
||||
None => return false,
|
||||
};
|
||||
for excl in &self.exclude {
|
||||
if is_prefix(excl, path) && is_prefix(include, excl) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
|
||||
fn is_prefix(short: &AbsPath, long: &AbsPath) -> bool {
|
||||
long.strip_prefix(short).is_some()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn dirs(base: AbsPathBuf, exclude: &[&str]) -> Directories {
|
||||
let exclude = exclude.iter().map(|it| base.join(it)).collect::<Vec<_>>();
|
||||
Directories { extensions: vec!["rs".to_string()], include: vec![base], exclude }
|
||||
}
|
||||
|
||||
impl fmt::Debug for Message {
|
||||
|
|
|
@ -54,7 +54,6 @@ fn check_licenses() {
|
|||
let expected = "
|
||||
0BSD OR MIT OR Apache-2.0
|
||||
Apache-2.0
|
||||
Apache-2.0 / MIT
|
||||
Apache-2.0 OR BSL-1.0
|
||||
Apache-2.0 OR MIT
|
||||
Apache-2.0 WITH LLVM-exception OR Apache-2.0 OR MIT
|
||||
|
|
Loading…
Reference in a new issue