From 701da3c726cf73453d7fed7266f55673341dcc35 Mon Sep 17 00:00:00 2001 From: Ville Penttinen Date: Mon, 18 Mar 2019 15:41:38 +0200 Subject: [PATCH 1/6] Implement Root based filtering for files and folders in Vfs The filtering is done through implementing the trait `Filter` which is then applied to folders and files under the given `RootEntry`. --- src/io.rs | 11 +-- src/lib.rs | 10 +-- src/roots.rs | 203 ++++++++++++++++++++++++++++++++++++--------------- tests/vfs.rs | 113 +++++++++++++++++++++++++++- 4 files changed, 266 insertions(+), 71 deletions(-) diff --git a/src/io.rs b/src/io.rs index 5969ee0d..9105e34c 100644 --- a/src/io.rs +++ b/src/io.rs @@ -10,7 +10,7 @@ use relative_path::RelativePathBuf; use walkdir::WalkDir; use notify::{DebouncedEvent, RecommendedWatcher, RecursiveMode, Watcher as _Watcher}; -use crate::{Roots, VfsRoot, VfsTask}; +use crate::{Roots, VfsRoot, VfsTask, roots::FileType}; pub(crate) enum Task { AddRoot { root: VfsRoot }, @@ -220,14 +220,15 @@ fn handle_change( path: PathBuf, kind: ChangeKind, ) { - let (root, rel_path) = match roots.find(&path) { + let ft = if path.is_file() { FileType::File } else { FileType::Dir }; + let (root, rel_path) = match roots.find(&path, ft) { None => return, Some(it) => it, }; match kind { ChangeKind::Create => { let mut paths = Vec::new(); - if path.is_dir() { + if ft.is_dir() { paths.extend(watch_recursive(watcher, &path, roots, root)); } else { paths.push(rel_path); @@ -259,7 +260,7 @@ fn watch_recursive( let mut files = Vec::new(); for entry in WalkDir::new(dir) .into_iter() - .filter_entry(|it| roots.contains(root, it.path()).is_some()) + .filter_entry(|it| roots.contains(root, it.path(), it.file_type().into()).is_some()) .filter_map(|it| it.map_err(|e| log::warn!("watcher error: {}", e)).ok()) { if entry.file_type().is_dir() { @@ -267,7 +268,7 @@ fn watch_recursive( watch_one(watcher, entry.path()); } } else { - let path = roots.contains(root, entry.path()).unwrap(); + let path = roots.contains(root, entry.path(), FileType::File).unwrap(); files.push(path.to_owned()); } } diff --git a/src/lib.rs b/src/lib.rs index 79c7ccee..c95ee6c2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,15 +25,15 @@ use std::{ }; use crossbeam_channel::Receiver; -use relative_path::{RelativePath, RelativePathBuf}; +pub use relative_path::{RelativePath, RelativePathBuf}; use rustc_hash::{FxHashMap, FxHashSet}; use crate::{ io::{TaskResult, Worker}, - roots::Roots, + roots::{Roots, FileType}, }; -pub use crate::roots::VfsRoot; +pub use crate::roots::{VfsRoot, RootEntry, Filter}; /// Opaque wrapper around file-system event. /// @@ -85,7 +85,7 @@ pub enum VfsChange { } impl Vfs { - pub fn new(roots: Vec) -> (Vfs, Vec) { + pub fn new(roots: Vec) -> (Vfs, Vec) { let roots = Arc::new(Roots::new(roots)); let worker = io::start(Arc::clone(&roots)); let mut root2files = FxHashMap::default(); @@ -281,7 +281,7 @@ impl Vfs { } fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option)> { - let (root, path) = self.roots.find(&path)?; + let (root, path) = self.roots.find(&path, FileType::File)?; let file = self.find_file(root, &path); Some((root, path, file)) } diff --git a/src/roots.rs b/src/roots.rs index 2bf7b0e0..31a11de8 100644 --- a/src/roots.rs +++ b/src/roots.rs @@ -5,17 +5,75 @@ use std::{ use relative_path::{ RelativePath, RelativePathBuf}; +/// a `Filter` is used to determine whether a file or a folder +/// under the specific root is included. +pub trait Filter: Send + Sync { + fn include_folder(&self, folder_path: &RelativePath) -> bool; + fn include_file(&self, file_path: &RelativePath) -> bool; +} + +/// RootEntry identifies a root folder with a given filter +/// used to determine whether to include or exclude files and folders under it. +pub struct RootEntry { + path: PathBuf, + filter: Box, +} + +impl std::fmt::Debug for RootEntry { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "RootEntry({})", self.path.display()) + } +} + +impl Eq for RootEntry {} +impl PartialEq for RootEntry { + fn eq(&self, other: &Self) -> bool { + // Entries are equal based on their paths + self.path == other.path + } +} + +impl RootEntry { + /// Create a new `RootEntry` with the given `filter` applied to + /// files and folder under it. + pub fn new(path: PathBuf, filter: Box) -> Self { + RootEntry { path, filter } + } +} + /// VfsRoot identifies a watched directory on the file system. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] pub struct VfsRoot(pub u32); +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub(crate) enum FileType { + File, + Dir, +} + +impl FileType { + pub(crate) fn is_dir(&self) -> bool { + *self == FileType::Dir + } +} + +impl std::convert::From for FileType { + fn from(v: std::fs::FileType) -> Self { + if v.is_file() { + FileType::File + } else { + FileType::Dir + } + } +} + /// Describes the contents of a single source root. /// -/// `RootConfig` can be thought of as a glob pattern like `src/**.rs` which +/// `RootData` can be thought of as a glob pattern like `src/**.rs` which /// specifies the source root or as a function which takes a `PathBuf` and -/// returns `true` iff path belongs to the source root +/// returns `true` if path belongs to the source root struct RootData { - path: PathBuf, + entry: RootEntry, // result of `root.canonicalize()` if that differs from `root`; `None` otherwise. canonical_path: Option, excluded_dirs: Vec, @@ -26,22 +84,39 @@ pub(crate) struct Roots { } impl Roots { - pub(crate) fn new(mut paths: Vec) -> Roots { - let mut roots = Vec::new(); + pub(crate) fn new(mut paths: Vec) -> Roots { // A hack to make nesting work. - paths.sort_by_key(|it| std::cmp::Reverse(it.as_os_str().len())); + paths.sort_by_key(|it| std::cmp::Reverse(it.path.as_os_str().len())); paths.dedup(); - for (i, path) in paths.iter().enumerate() { - let nested_roots = - paths[..i].iter().filter_map(|it| rel_path(path, it)).collect::>(); - roots.push(RootData::new(path.clone(), nested_roots)); - } + // First gather all the nested roots for each path + let nested_roots = paths + .iter() + .enumerate() + .map(|(i, entry)| { + paths[..i] + .iter() + .filter_map(|it| rel_path(&entry.path, &it.path)) + .collect::>() + }) + .collect::>(); + + // Then combine the entry with the matching nested_roots + let roots = paths + .into_iter() + .zip(nested_roots.into_iter()) + .map(|(entry, nested_roots)| RootData::new(entry, nested_roots)) + .collect::>(); + Roots { roots } } - pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> { + pub(crate) fn find( + &self, + path: &Path, + expected: FileType, + ) -> Option<(VfsRoot, RelativePathBuf)> { self.iter().find_map(|root| { - let rel_path = self.contains(root, path)?; + let rel_path = self.contains(root, path, expected)?; Some((root, rel_path)) }) } @@ -52,16 +127,21 @@ impl Roots { (0..self.roots.len()).into_iter().map(|idx| VfsRoot(idx as u32)) } pub(crate) fn path(&self, root: VfsRoot) -> &Path { - self.root(root).path.as_path() + self.root(root).path().as_path() } - /// Checks if root contains a path and returns a root-relative path. - pub(crate) fn contains(&self, root: VfsRoot, path: &Path) -> Option { + + /// Checks if root contains a path with the given `FileType` + /// and returns a root-relative path. + pub(crate) fn contains( + &self, + root: VfsRoot, + path: &Path, + expected: FileType, + ) -> Option { let data = self.root(root); - iter::once(&data.path) + iter::once(data.path()) .chain(data.canonical_path.as_ref().into_iter()) - .find_map(|base| rel_path(base, path)) - .filter(|path| !data.excluded_dirs.contains(path)) - .filter(|path| !data.is_excluded(path)) + .find_map(|base| to_relative_path(base, path, &data, expected)) } fn root(&self, root: VfsRoot) -> &RootData { @@ -70,58 +150,63 @@ impl Roots { } impl RootData { - fn new(path: PathBuf, excluded_dirs: Vec) -> RootData { - let mut canonical_path = path.canonicalize().ok(); - if Some(&path) == canonical_path.as_ref() { + pub fn new(entry: RootEntry, excluded_dirs: Vec) -> Self { + let mut canonical_path = entry.path.canonicalize().ok(); + if Some(&entry.path) == canonical_path.as_ref() { canonical_path = None; } - RootData { path, canonical_path, excluded_dirs } + RootData { entry, canonical_path, excluded_dirs } } - fn is_excluded(&self, path: &RelativePath) -> bool { - if self.excluded_dirs.iter().any(|it| it == path) { - return true; - } - // Ignore some common directories. - // - // FIXME: don't hard-code, specify at source-root creation time using - // gitignore - for (i, c) in path.components().enumerate() { - if let relative_path::Component::Normal(c) = c { - if (i == 0 && c == "target") || c == ".git" || c == "node_modules" { - return true; - } - } - } - - match path.extension() { - Some("rs") => false, - Some(_) => true, - // Exclude extension-less and hidden files - None => is_extensionless_or_hidden_file(&self.path, path), - } + fn path(&self) -> &PathBuf { + &self.entry.path } -} -fn is_extensionless_or_hidden_file>(base: P, relative_path: &RelativePath) -> bool { - // Exclude files/paths starting with "." - if relative_path.file_stem().map(|s| s.starts_with(".")).unwrap_or(false) { - return true; - } + /// Returns true if the given `RelativePath` is included inside this `RootData` + fn is_included(&self, rel_path: &RelativePathBuf, expected: FileType) -> bool { + if self.excluded_dirs.contains(&rel_path) { + return false; + } - if relative_path.extension().is_some() { - return false; - } + let parent_included = + rel_path.parent().map(|d| self.entry.filter.include_folder(&d)).unwrap_or(true); - let path = relative_path.to_path(base); + if !parent_included { + return false; + } - std::fs::metadata(path) - .map(|m| m.is_file()) - .unwrap_or(false) + match expected { + FileType::File => self.entry.filter.include_file(&rel_path), + FileType::Dir => self.entry.filter.include_folder(&rel_path), + } + } } +/// Returns the path relative to `base` fn rel_path(base: &Path, path: &Path) -> Option { let path = path.strip_prefix(base).ok()?; let path = RelativePathBuf::from_path(path).unwrap(); Some(path) } + +/// Returns the path relative to `base` with filtering applied based on `data` +fn to_relative_path( + base: &Path, + path: &Path, + data: &RootData, + expected: FileType, +) -> Option { + let rel_path = rel_path(base, path)?; + + // Apply filtering _only_ if the relative path is non-empty + // if it's empty, it means we are currently processing the root + if rel_path.as_str().is_empty() { + return Some(rel_path); + } + + if data.is_included(&rel_path, expected) { + Some(rel_path) + } else { + None + } +} diff --git a/tests/vfs.rs b/tests/vfs.rs index 6587cfaa..5caaa0cf 100644 --- a/tests/vfs.rs +++ b/tests/vfs.rs @@ -2,7 +2,7 @@ use std::{collections::HashSet, fs, time::Duration}; // use flexi_logger::Logger; use crossbeam_channel::RecvTimeoutError; -use ra_vfs::{Vfs, VfsChange}; +use ra_vfs::{Vfs, VfsChange, RootEntry, Filter, RelativePath}; use tempfile::tempdir; /// Processes exactly `num_tasks` events waiting in the `vfs` message queue. @@ -40,6 +40,112 @@ macro_rules! assert_match { }; } +struct IncludeRustFiles; + +impl IncludeRustFiles { + fn boxed() -> Box { + Box::new(Self {}) + } +} + +impl Filter for IncludeRustFiles { + fn include_folder(&self, folder_path: &RelativePath) -> bool { + const IGNORED_FOLDERS: &[&str] = &["node_modules", "target", ".git"]; + + let is_ignored = folder_path.components().any(|c| IGNORED_FOLDERS.contains(&c.as_str())); + + let hidden = folder_path.file_stem().map(|s| s.starts_with(".")).unwrap_or(false); + + !is_ignored && !hidden + } + + fn include_file(&self, file_path: &RelativePath) -> bool { + file_path.extension() == Some("rs") + } +} + +#[test] +fn test_vfs_ignore() -> std::io::Result<()> { + // flexi_logger::Logger::with_str("vfs=debug,ra_vfs=debug").start().unwrap(); + + let files = [ + ("ignore_a/foo.rs", "hello"), + ("ignore_a/bar.rs", "world"), + ("ignore_a/b/baz.rs", "nested hello"), + ("ignore_a/LICENSE", "extensionless file"), + ("ignore_a/b/AUTHOR", "extensionless file"), + ("ignore_a/.hidden.txt", "hidden file"), + ("ignore_a/.hidden_folder/file.rs", "hidden folder containing rust file"), + ( + "ignore_a/.hidden_folder/nested/foo.rs", + "file inside nested folder inside a hidden folder", + ), + ("ignore_a/node_modules/module.js", "hidden file js"), + ("ignore_a/node_modules/module2.rs", "node rust"), + ("ignore_a/node_modules/nested/foo.bar", "hidden file bar"), + ]; + + let dir = tempdir().unwrap(); + for (path, text) in files.iter() { + let file_path = dir.path().join(path); + fs::create_dir_all(file_path.parent().unwrap()).unwrap(); + fs::write(file_path, text)? + } + + let a_root = dir.path().join("ignore_a"); + let b_root = dir.path().join("ignore_a/b"); + + let (mut vfs, _) = Vfs::new(vec![ + RootEntry::new(a_root, IncludeRustFiles::boxed()), + RootEntry::new(b_root, IncludeRustFiles::boxed()), + ]); + process_tasks(&mut vfs, 2); + { + let files = vfs + .commit_changes() + .into_iter() + .flat_map(|change| { + let files = match change { + VfsChange::AddRoot { files, .. } => files, + _ => panic!("unexpected change"), + }; + files.into_iter().map(|(_id, path, text)| { + let text: String = (&*text).clone(); + (format!("{}", path.display()), text) + }) + }) + .collect::>(); + + let expected_files = [("foo.rs", "hello"), ("bar.rs", "world"), ("baz.rs", "nested hello")] + .iter() + .map(|(path, text)| (path.to_string(), text.to_string())) + .collect::>(); + + assert_eq!(files, expected_files); + } + + // rust-analyzer#734: fsevents has a bunch of events still sitting around. + process_tasks_in_range(&mut vfs, 0, if cfg!(target_os = "macos") { 7 } else { 0 }); + assert!(vfs.commit_changes().is_empty()); + + // These will get filtered out + vfs.add_file_overlay(&dir.path().join("ignore_a/node_modules/spam.rs"), "spam".to_string()); + vfs.add_file_overlay(&dir.path().join("ignore_a/node_modules/spam2.rs"), "spam".to_string()); + vfs.add_file_overlay(&dir.path().join("ignore_a/node_modules/spam3.rs"), "spam".to_string()); + vfs.add_file_overlay(&dir.path().join("ignore_a/LICENSE2"), "text".to_string()); + assert_match!(vfs.commit_changes().as_slice(), []); + + fs::create_dir_all(dir.path().join("ignore_a/node_modules/sub1")).unwrap(); + fs::write(dir.path().join("ignore_a/node_modules/sub1/new.rs"), "new hello").unwrap(); + + assert_match!( + vfs.task_receiver().recv_timeout(Duration::from_millis(300)), // slightly more than watcher debounce delay + Err(RecvTimeoutError::Timeout) + ); + + Ok(()) +} + #[test] fn test_vfs_works() -> std::io::Result<()> { // Logger::with_str("vfs=debug,ra_vfs=debug").start().unwrap(); @@ -63,7 +169,10 @@ fn test_vfs_works() -> std::io::Result<()> { let a_root = dir.path().join("a"); let b_root = dir.path().join("a/b"); - let (mut vfs, _) = Vfs::new(vec![a_root, b_root]); + let (mut vfs, _) = Vfs::new(vec![ + RootEntry::new(a_root, IncludeRustFiles::boxed()), + RootEntry::new(b_root, IncludeRustFiles::boxed()), + ]); process_tasks(&mut vfs, 2); { let files = vfs From c0784cd4b98c5a6594da04b0764b2db79f4c22f1 Mon Sep 17 00:00:00 2001 From: Ville Penttinen Date: Mon, 18 Mar 2019 16:07:26 +0200 Subject: [PATCH 2/6] Add some documentation to Filter --- src/roots.rs | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/src/roots.rs b/src/roots.rs index 31a11de8..52209083 100644 --- a/src/roots.rs +++ b/src/roots.rs @@ -7,6 +7,35 @@ use relative_path::{ RelativePath, RelativePathBuf}; /// a `Filter` is used to determine whether a file or a folder /// under the specific root is included. +/// +/// *NOTE*: If the parent folder of a file is not included, then +/// `include_file` will not be called. +/// +/// # Example +/// +/// Implementing `Filter` for rust files: +/// +/// ``` +/// use ra_vfs::{Filter, RelativePath}; +/// +/// struct IncludeRustFiles; +/// +/// impl Filter for IncludeRustFiles { +/// fn include_folder(&self, folder_path: &RelativePath) -> bool { +/// // These folders are ignored +/// const IGNORED_FOLDERS: &[&str] = &["node_modules", "target", ".git"]; +/// +/// let is_ignored = folder_path.components().any(|c| IGNORED_FOLDERS.contains(&c.as_str())); +/// +/// !is_ignored +/// } +/// +/// fn include_file(&self, file_path: &RelativePath) -> bool { +/// // Only include rust files +/// file_path.extension() == Some("rs") +/// } +/// } +/// ``` pub trait Filter: Send + Sync { fn include_folder(&self, folder_path: &RelativePath) -> bool; fn include_file(&self, file_path: &RelativePath) -> bool; From 08ba3f0827da80ab2417c701622f0253dd8fe9fa Mon Sep 17 00:00:00 2001 From: Ville Penttinen Date: Mon, 18 Mar 2019 18:04:15 +0200 Subject: [PATCH 3/6] Rename include_folder to include_dir This matches better with other Rust APIs --- src/roots.rs | 10 +++++----- tests/vfs.rs | 6 +++--- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/roots.rs b/src/roots.rs index 52209083..ff38c194 100644 --- a/src/roots.rs +++ b/src/roots.rs @@ -21,11 +21,11 @@ use relative_path::{ RelativePath, RelativePathBuf}; /// struct IncludeRustFiles; /// /// impl Filter for IncludeRustFiles { -/// fn include_folder(&self, folder_path: &RelativePath) -> bool { +/// fn include_dir(&self, dir_path: &RelativePath) -> bool { /// // These folders are ignored /// const IGNORED_FOLDERS: &[&str] = &["node_modules", "target", ".git"]; /// -/// let is_ignored = folder_path.components().any(|c| IGNORED_FOLDERS.contains(&c.as_str())); +/// let is_ignored = dir_path.components().any(|c| IGNORED_FOLDERS.contains(&c.as_str())); /// /// !is_ignored /// } @@ -37,7 +37,7 @@ use relative_path::{ RelativePath, RelativePathBuf}; /// } /// ``` pub trait Filter: Send + Sync { - fn include_folder(&self, folder_path: &RelativePath) -> bool; + fn include_dir(&self, dir_path: &RelativePath) -> bool; fn include_file(&self, file_path: &RelativePath) -> bool; } @@ -198,7 +198,7 @@ impl RootData { } let parent_included = - rel_path.parent().map(|d| self.entry.filter.include_folder(&d)).unwrap_or(true); + rel_path.parent().map(|d| self.entry.filter.include_dir(&d)).unwrap_or(true); if !parent_included { return false; @@ -206,7 +206,7 @@ impl RootData { match expected { FileType::File => self.entry.filter.include_file(&rel_path), - FileType::Dir => self.entry.filter.include_folder(&rel_path), + FileType::Dir => self.entry.filter.include_dir(&rel_path), } } } diff --git a/tests/vfs.rs b/tests/vfs.rs index 5caaa0cf..09c0acae 100644 --- a/tests/vfs.rs +++ b/tests/vfs.rs @@ -49,12 +49,12 @@ impl IncludeRustFiles { } impl Filter for IncludeRustFiles { - fn include_folder(&self, folder_path: &RelativePath) -> bool { + fn include_dir(&self, dir_path: &RelativePath) -> bool { const IGNORED_FOLDERS: &[&str] = &["node_modules", "target", ".git"]; - let is_ignored = folder_path.components().any(|c| IGNORED_FOLDERS.contains(&c.as_str())); + let is_ignored = dir_path.components().any(|c| IGNORED_FOLDERS.contains(&c.as_str())); - let hidden = folder_path.file_stem().map(|s| s.starts_with(".")).unwrap_or(false); + let hidden = dir_path.file_stem().map(|s| s.starts_with(".")).unwrap_or(false); !is_ignored && !hidden } From dea479ce55de7c4125e2ba641f1f11fe4dc3c95e Mon Sep 17 00:00:00 2001 From: Ville Penttinen Date: Mon, 18 Mar 2019 18:14:38 +0200 Subject: [PATCH 4/6] Move Filter and RootEntry to lib.rs --- src/lib.rs | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++- src/roots.rs | 67 ++-------------------------------------------------- 2 files changed, 67 insertions(+), 66 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c95ee6c2..91fa3929 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,8 +33,72 @@ use crate::{ roots::{Roots, FileType}, }; -pub use crate::roots::{VfsRoot, RootEntry, Filter}; +pub use crate::roots::{VfsRoot}; +/// a `Filter` is used to determine whether a file or a folder +/// under the specific root is included. +/// +/// *NOTE*: If the parent folder of a file is not included, then +/// `include_file` will not be called. +/// +/// # Example +/// +/// Implementing `Filter` for rust files: +/// +/// ``` +/// use ra_vfs::{Filter, RelativePath}; +/// +/// struct IncludeRustFiles; +/// +/// impl Filter for IncludeRustFiles { +/// fn include_dir(&self, dir_path: &RelativePath) -> bool { +/// // These folders are ignored +/// const IGNORED_FOLDERS: &[&str] = &["node_modules", "target", ".git"]; +/// +/// let is_ignored = dir_path.components().any(|c| IGNORED_FOLDERS.contains(&c.as_str())); +/// +/// !is_ignored +/// } +/// +/// fn include_file(&self, file_path: &RelativePath) -> bool { +/// // Only include rust files +/// file_path.extension() == Some("rs") +/// } +/// } +/// ``` +pub trait Filter: Send + Sync { + fn include_dir(&self, dir_path: &RelativePath) -> bool; + fn include_file(&self, file_path: &RelativePath) -> bool; +} + +/// RootEntry identifies a root folder with a given filter +/// used to determine whether to include or exclude files and folders under it. +pub struct RootEntry { + path: PathBuf, + filter: Box, +} + +impl std::fmt::Debug for RootEntry { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "RootEntry({})", self.path.display()) + } +} + +impl Eq for RootEntry {} +impl PartialEq for RootEntry { + fn eq(&self, other: &Self) -> bool { + // Entries are equal based on their paths + self.path == other.path + } +} + +impl RootEntry { + /// Create a new `RootEntry` with the given `filter` applied to + /// files and folder under it. + pub fn new(path: PathBuf, filter: Box) -> Self { + RootEntry { path, filter } + } +} /// Opaque wrapper around file-system event. /// /// Calling code is expected to just pass `VfsTask` to `handle_task` method. It diff --git a/src/roots.rs b/src/roots.rs index ff38c194..6a90ab5a 100644 --- a/src/roots.rs +++ b/src/roots.rs @@ -3,72 +3,9 @@ use std::{ path::{Path, PathBuf}, }; -use relative_path::{ RelativePath, RelativePathBuf}; +use relative_path::RelativePathBuf; -/// a `Filter` is used to determine whether a file or a folder -/// under the specific root is included. -/// -/// *NOTE*: If the parent folder of a file is not included, then -/// `include_file` will not be called. -/// -/// # Example -/// -/// Implementing `Filter` for rust files: -/// -/// ``` -/// use ra_vfs::{Filter, RelativePath}; -/// -/// struct IncludeRustFiles; -/// -/// impl Filter for IncludeRustFiles { -/// fn include_dir(&self, dir_path: &RelativePath) -> bool { -/// // These folders are ignored -/// const IGNORED_FOLDERS: &[&str] = &["node_modules", "target", ".git"]; -/// -/// let is_ignored = dir_path.components().any(|c| IGNORED_FOLDERS.contains(&c.as_str())); -/// -/// !is_ignored -/// } -/// -/// fn include_file(&self, file_path: &RelativePath) -> bool { -/// // Only include rust files -/// file_path.extension() == Some("rs") -/// } -/// } -/// ``` -pub trait Filter: Send + Sync { - fn include_dir(&self, dir_path: &RelativePath) -> bool; - fn include_file(&self, file_path: &RelativePath) -> bool; -} - -/// RootEntry identifies a root folder with a given filter -/// used to determine whether to include or exclude files and folders under it. -pub struct RootEntry { - path: PathBuf, - filter: Box, -} - -impl std::fmt::Debug for RootEntry { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "RootEntry({})", self.path.display()) - } -} - -impl Eq for RootEntry {} -impl PartialEq for RootEntry { - fn eq(&self, other: &Self) -> bool { - // Entries are equal based on their paths - self.path == other.path - } -} - -impl RootEntry { - /// Create a new `RootEntry` with the given `filter` applied to - /// files and folder under it. - pub fn new(path: PathBuf, filter: Box) -> Self { - RootEntry { path, filter } - } -} +use super::{RootEntry, Filter}; /// VfsRoot identifies a watched directory on the file system. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)] From 9c1ad9d3e274015b48397fa8ac2beb17b861d8a8 Mon Sep 17 00:00:00 2001 From: Ville Penttinen Date: Mon, 18 Mar 2019 18:18:17 +0200 Subject: [PATCH 5/6] Use root and filter directly in RootData --- src/roots.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/roots.rs b/src/roots.rs index 6a90ab5a..2381babd 100644 --- a/src/roots.rs +++ b/src/roots.rs @@ -39,7 +39,8 @@ impl std::convert::From for FileType { /// specifies the source root or as a function which takes a `PathBuf` and /// returns `true` if path belongs to the source root struct RootData { - entry: RootEntry, + root: PathBuf, + filter: Box, // result of `root.canonicalize()` if that differs from `root`; `None` otherwise. canonical_path: Option, excluded_dirs: Vec, @@ -121,29 +122,29 @@ impl RootData { if Some(&entry.path) == canonical_path.as_ref() { canonical_path = None; } - RootData { entry, canonical_path, excluded_dirs } + RootData { root: entry.path, filter: entry.filter, canonical_path, excluded_dirs } } fn path(&self) -> &PathBuf { - &self.entry.path + &self.root } /// Returns true if the given `RelativePath` is included inside this `RootData` fn is_included(&self, rel_path: &RelativePathBuf, expected: FileType) -> bool { - if self.excluded_dirs.contains(&rel_path) { + if self.excluded_dirs.iter().any(|d| rel_path.starts_with(d)) { return false; } let parent_included = - rel_path.parent().map(|d| self.entry.filter.include_dir(&d)).unwrap_or(true); + rel_path.parent().map(|d| self.filter.include_dir(&d)).unwrap_or(true); if !parent_included { return false; } match expected { - FileType::File => self.entry.filter.include_file(&rel_path), - FileType::Dir => self.entry.filter.include_dir(&rel_path), + FileType::File => self.filter.include_file(&rel_path), + FileType::Dir => self.filter.include_dir(&rel_path), } } } From d7e2fa68515678c76a15011caee14979422b3353 Mon Sep 17 00:00:00 2001 From: Ville Penttinen Date: Mon, 18 Mar 2019 18:29:01 +0200 Subject: [PATCH 6/6] Remove unnecessary braces and unnecessary pub --- src/lib.rs | 2 +- src/roots.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 91fa3929..efeccdf8 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -33,7 +33,7 @@ use crate::{ roots::{Roots, FileType}, }; -pub use crate::roots::{VfsRoot}; +pub use crate::roots::VfsRoot; /// a `Filter` is used to determine whether a file or a folder /// under the specific root is included. diff --git a/src/roots.rs b/src/roots.rs index 2381babd..0998d611 100644 --- a/src/roots.rs +++ b/src/roots.rs @@ -117,7 +117,7 @@ impl Roots { } impl RootData { - pub fn new(entry: RootEntry, excluded_dirs: Vec) -> Self { + fn new(entry: RootEntry, excluded_dirs: Vec) -> RootData { let mut canonical_path = entry.path.canonicalize().ok(); if Some(&entry.path) == canonical_path.as_ref() { canonical_path = None;