From 311ec70d03c27b1b37457ef44510e735fcce0885 Mon Sep 17 00:00:00 2001 From: Arnaud Date: Tue, 12 Jan 2021 17:22:57 +0100 Subject: Document vfs public items --- crates/vfs/src/anchored_path.rs | 10 +++++++ crates/vfs/src/file_set.rs | 42 ++++++++++++++++++++++++++ crates/vfs/src/lib.rs | 50 +++++++++++++++++++++++++++++++ crates/vfs/src/loader.rs | 65 +++++++++++++++++++++++++++++++++++++++++ crates/vfs/src/vfs_path.rs | 39 +++++++++++++++++++++++-- 5 files changed, 204 insertions(+), 2 deletions(-) (limited to 'crates/vfs') diff --git a/crates/vfs/src/anchored_path.rs b/crates/vfs/src/anchored_path.rs index 02720a32e..db15a2a21 100644 --- a/crates/vfs/src/anchored_path.rs +++ b/crates/vfs/src/anchored_path.rs @@ -26,14 +26,24 @@ //! from the anchor than. use crate::FileId; +/// Path relative to a file. +/// +/// Owned version of [`AnchoredPath`]. #[derive(Clone, PartialEq, Eq, Debug)] pub struct AnchoredPathBuf { + /// File that this path is relative to. pub anchor: FileId, + /// Path relative to `anchor`'s containing directory. pub path: String, } +/// Path relative to a file. +/// +/// Borrowed version of [`AnchoredPathBuf`]. #[derive(Clone, Copy, PartialEq, Eq, Debug)] pub struct AnchoredPath<'a> { + /// File that this path is relative to. pub anchor: FileId, + /// Path relative to `anchor`'s containing directory. pub path: &'a str, } diff --git a/crates/vfs/src/file_set.rs b/crates/vfs/src/file_set.rs index 49ca593ac..348b6dbfd 100644 --- a/crates/vfs/src/file_set.rs +++ b/crates/vfs/src/file_set.rs @@ -9,6 +9,7 @@ use rustc_hash::FxHashMap; use crate::{AnchoredPath, FileId, Vfs, VfsPath}; +/// A set of [`VfsPath`]s identified by [`FileId`]s. #[derive(Default, Clone, Eq, PartialEq)] pub struct FileSet { files: FxHashMap, @@ -16,9 +17,15 @@ pub struct FileSet { } impl FileSet { + /// Returns the number of stored paths. pub fn len(&self) -> usize { self.files.len() } + + /// Get the id of the file corresponding to `path`. + /// + /// If either `path`'s [`anchor`](AnchoredPath::anchor) or the resolved path is not in + /// the set, returns [`None`]. pub fn resolve_path(&self, path: AnchoredPath<'_>) -> Option { let mut base = self.paths[&path.anchor].clone(); base.pop(); @@ -26,19 +33,26 @@ impl FileSet { self.files.get(&path).copied() } + /// Get the id corresponding to `path` if it exists in the set. pub fn file_for_path(&self, path: &VfsPath) -> Option<&FileId> { self.files.get(path) } + /// Get the path corresponding to `file` if it exists in the set. pub fn path_for_file(&self, file: &FileId) -> Option<&VfsPath> { self.paths.get(file) } + /// Insert the `file_id, path` pair into the set. + /// + /// # Note + /// Multiple [`FileId`] can be mapped to the same [`VfsPath`], and vice-versa. pub fn insert(&mut self, file_id: FileId, path: VfsPath) { self.files.insert(path.clone(), file_id); self.paths.insert(file_id, path); } + /// Iterate over this set's ids. pub fn iter(&self) -> impl Iterator + '_ { self.paths.keys().copied() } @@ -50,6 +64,23 @@ impl fmt::Debug for FileSet { } } +/// This contains path prefixes to partition a [`Vfs`] into [`FileSet`]s. +/// +/// # Example +/// ```rust +/// # use vfs::{file_set::FileSetConfigBuilder, VfsPath, Vfs}; +/// let mut builder = FileSetConfigBuilder::default(); +/// builder.add_file_set(vec![VfsPath::new_virtual_path("/src".to_string())]); +/// let config = builder.build(); +/// let mut file_system = Vfs::default(); +/// file_system.set_file_contents(VfsPath::new_virtual_path("/src/main.rs".to_string()), Some(vec![])); +/// file_system.set_file_contents(VfsPath::new_virtual_path("/src/lib.rs".to_string()), Some(vec![])); +/// file_system.set_file_contents(VfsPath::new_virtual_path("/build.rs".to_string()), Some(vec![])); +/// // contains the sets : +/// // { "/src/main.rs", "/src/lib.rs" } +/// // { "build.rs" } +/// let sets = config.partition(&file_system); +/// ``` #[derive(Debug)] pub struct FileSetConfig { n_file_sets: usize, @@ -63,9 +94,14 @@ impl Default for FileSetConfig { } impl FileSetConfig { + /// Returns a builder for `FileSetConfig`. pub fn builder() -> FileSetConfigBuilder { FileSetConfigBuilder::default() } + + /// Partition `vfs` into `FileSet`s. + /// + /// Creates a new [`FileSet`] for every set of prefixes in `self`. pub fn partition(&self, vfs: &Vfs) -> Vec { let mut scratch_space = Vec::new(); let mut res = vec![FileSet::default(); self.len()]; @@ -91,6 +127,7 @@ impl FileSetConfig { } } +/// Builder for [`FileSetConfig`]. pub struct FileSetConfigBuilder { roots: Vec>, } @@ -102,12 +139,17 @@ impl Default for FileSetConfigBuilder { } impl FileSetConfigBuilder { + /// Returns the number of sets currently held. pub fn len(&self) -> usize { self.roots.len() } + + /// Add a new set of paths prefixes. pub fn add_file_set(&mut self, roots: Vec) { self.roots.push(roots) } + + /// Build the `FileSetConfig`. pub fn build(self) -> FileSetConfig { let n_file_sets = self.roots.len() + 1; let map = { diff --git a/crates/vfs/src/lib.rs b/crates/vfs/src/lib.rs index 2b7b14524..bae2c6118 100644 --- a/crates/vfs/src/lib.rs +++ b/crates/vfs/src/lib.rs @@ -53,9 +53,15 @@ pub use crate::{ }; pub use paths::{AbsPath, AbsPathBuf}; +/// Handle to a file in [`Vfs`] +/// +/// Most functions in rust-analyzer use this when they need to refer to a file. #[derive(Copy, Clone, Debug, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct FileId(pub u32); +/// Storage for all files read by rust-analyzer. +/// +/// For more informations see the [crate-level](crate) documentation. #[derive(Default)] pub struct Vfs { interner: PathInterner, @@ -63,40 +69,73 @@ pub struct Vfs { changes: Vec, } +/// Changed file in the [`Vfs`]. pub struct ChangedFile { + /// Id of the changed file pub file_id: FileId, + /// Kind of change pub change_kind: ChangeKind, } impl ChangedFile { + /// Returns `true` if the change is not [`Delete`](ChangeKind::Delete). pub fn exists(&self) -> bool { self.change_kind != ChangeKind::Delete } + + /// Returns `true` if the change is [`Create`](ChangeKind::Create) or + /// [`Delete`](ChangeKind::Delete). pub fn is_created_or_deleted(&self) -> bool { matches!(self.change_kind, ChangeKind::Create | ChangeKind::Delete) } } +/// Kind of [file change](ChangedFile). #[derive(Eq, PartialEq, Copy, Clone, Debug)] pub enum ChangeKind { + /// The file was (re-)created Create, + /// The file was modified Modify, + /// The file was deleted Delete, } impl Vfs { + /// Amount of files currently stored. + /// + /// Note that this includes deleted files. pub fn len(&self) -> usize { self.data.len() } + + /// Id of the given path if it exists in the `Vfs` and is not deleted. pub fn file_id(&self, path: &VfsPath) -> Option { self.interner.get(path).filter(|&it| self.get(it).is_some()) } + + /// File path corresponding to the given `file_id`. + /// + /// # Panics + /// + /// Panics if the id is not present in the `Vfs`. pub fn file_path(&self, file_id: FileId) -> VfsPath { self.interner.lookup(file_id).clone() } + + /// File content corresponding to the given `file_id`. + /// + /// # Panics + /// + /// Panics if the id is not present in the `Vfs`, or if the corresponding file is + /// deleted. pub fn file_contents(&self, file_id: FileId) -> &[u8] { self.get(file_id).as_deref().unwrap() } + + /// Returns an iterator over the stored ids and their corresponding paths. + /// + /// This will skip deleted files. pub fn iter(&self) -> impl Iterator + '_ { (0..self.data.len()) .map(|it| FileId(it as u32)) @@ -106,6 +145,13 @@ impl Vfs { (file_id, path) }) } + + /// Update the `path` with the given `contents`. `None` means the file was deleted. + /// + /// Returns `true` if the file was modified, and saves the [change](ChangedFile). + /// + /// If the path does not currently exists in the `Vfs`, allocates a new + /// [`FileId`] for it. pub fn set_file_contents(&mut self, path: VfsPath, contents: Option>) -> bool { let file_id = self.alloc_file_id(path); let change_kind = match (&self.get(file_id), &contents) { @@ -120,9 +166,13 @@ impl Vfs { self.changes.push(ChangedFile { file_id, change_kind }); true } + + /// Returns `true` if the `Vfs` contains [changes](ChangedFile). pub fn has_changes(&self) -> bool { !self.changes.is_empty() } + + /// Drain and returns all the changes in the `Vfs`. pub fn take_changes(&mut self) -> Vec { mem::take(&mut self.changes) } diff --git a/crates/vfs/src/loader.rs b/crates/vfs/src/loader.rs index 40cf96020..399015043 100644 --- a/crates/vfs/src/loader.rs +++ b/crates/vfs/src/loader.rs @@ -3,9 +3,12 @@ use std::fmt; use paths::{AbsPath, AbsPathBuf}; +/// A set of files on the file system. #[derive(Debug, Clone)] pub enum Entry { + /// The `Entry` is represented by a raw set of files. Files(Vec), + /// The `Entry` is represented by `Directories`. Directories(Directories), } @@ -17,6 +20,8 @@ pub enum Entry { /// * it is not under `exclude` path /// /// If many include/exclude paths match, the longest one wins. +/// +/// If a path is in both `include` and `exclude`, the `exclude` one wins. #[derive(Debug, Clone, Default)] pub struct Directories { pub extensions: Vec, @@ -24,45 +29,99 @@ pub struct Directories { pub exclude: Vec, } +/// [`Handle`]'s configuration. #[derive(Debug)] pub struct Config { + /// Set of initially loaded files. pub load: Vec, + /// Index of watched entries in `load`. + /// + /// If a path in a watched entry is modified,the [`Handle`] should notify it. pub watch: Vec, } +/// Message about an action taken by a [`Handle`]. pub enum Message { + /// Indicate a gradual progress. + /// + /// This is supposed to be the number of loaded files. Progress { n_total: usize, n_done: usize }, + /// The handle loaded the following files' content. Loaded { files: Vec<(AbsPathBuf, Option>)> }, } +/// Type that will receive [`Messages`](Message) from a [`Handle`]. pub type Sender = Box; +/// Interface for reading and watching files. pub trait Handle: fmt::Debug { + /// Spawn a new handle with the given `sender`. fn spawn(sender: Sender) -> Self where Self: Sized; + + /// Set this handle's configuration. fn set_config(&mut self, config: Config); + + /// The file's content at `path` has been modified, and should be reloaded. fn invalidate(&mut self, path: AbsPathBuf); + + /// Load the content of the given file, returning [`None`] if it does not + /// exists. fn load_sync(&mut self, path: &AbsPath) -> Option>; } impl Entry { + /// Returns: + /// ```text + /// Entry::Directories(Directories { + /// extensions: ["rs"], + /// include: [base], + /// exclude: [base/.git], + /// }) + /// ``` pub fn rs_files_recursively(base: AbsPathBuf) -> Entry { Entry::Directories(dirs(base, &[".git"])) } + + /// Returns: + /// ```text + /// Entry::Directories(Directories { + /// extensions: ["rs"], + /// include: [base], + /// exclude: [base/.git, base/target], + /// }) + /// ``` pub fn local_cargo_package(base: AbsPathBuf) -> Entry { Entry::Directories(dirs(base, &[".git", "target"])) } + + /// Returns: + /// ```text + /// Entry::Directories(Directories { + /// extensions: ["rs"], + /// include: [base], + /// exclude: [base/.git, /tests, /examples, /benches], + /// }) + /// ``` pub fn cargo_package_dependency(base: AbsPathBuf) -> Entry { Entry::Directories(dirs(base, &[".git", "/tests", "/examples", "/benches"])) } + /// Returns `true` if `path` is included in `self`. + /// + /// See [`Directories::contains_file`]. 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), } } + + /// Returns `true` if `path` is included in `self`. + /// + /// - If `self` is `Entry::Files`, returns `false` + /// - Else, see [`Directories::contains_dir`]. pub fn contains_dir(&self, path: &AbsPath) -> bool { match self { Entry::Files(_) => false, @@ -72,6 +131,7 @@ impl Entry { } impl Directories { + /// Returns `true` if `path` is included in `self`. 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) { @@ -79,6 +139,11 @@ impl Directories { } self.includes_path(path) } + + /// Returns `true` if `path` is included in `self`. + /// + /// Since `path` is supposed to be a directory, this will not take extension + /// into account. pub fn contains_dir(&self, path: &AbsPath) -> bool { self.includes_path(path) } diff --git a/crates/vfs/src/vfs_path.rs b/crates/vfs/src/vfs_path.rs index bd14911c9..74b6333e2 100644 --- a/crates/vfs/src/vfs_path.rs +++ b/crates/vfs/src/vfs_path.rs @@ -3,25 +3,37 @@ use std::fmt; use paths::{AbsPath, AbsPathBuf}; +/// Path in [`Vfs`]. +/// /// Long-term, we want to support files which do not reside in the file-system, -/// so we treat VfsPaths as opaque identifiers. +/// so we treat `VfsPath`s as opaque identifiers. +/// +/// [`Vfs`]: crate::Vfs #[derive(Clone, Ord, PartialOrd, Eq, PartialEq, Hash)] pub struct VfsPath(VfsPathRepr); impl VfsPath { - /// Creates an "in-memory" path from `/`-separates string. + /// Creates an "in-memory" path from `/`-separated string. + /// /// This is most useful for testing, to avoid windows/linux differences + /// + /// # Panics + /// + /// Panics if `path` does not start with `'/'`. pub fn new_virtual_path(path: String) -> VfsPath { assert!(path.starts_with('/')); VfsPath(VfsPathRepr::VirtualPath(VirtualPath(path))) } + /// Returns the `AbsPath` representation of `self` if `self` is on the file system. pub fn as_path(&self) -> Option<&AbsPath> { match &self.0 { VfsPathRepr::PathBuf(it) => Some(it.as_path()), VfsPathRepr::VirtualPath(_) => None, } } + + /// Creates a new `VfsPath` with `path` adjoined to `self`. pub fn join(&self, path: &str) -> Option { match &self.0 { VfsPathRepr::PathBuf(it) => { @@ -34,12 +46,30 @@ impl VfsPath { } } } + + /// Remove the last component of `self` if there is one. + /// + /// If `self` has no component, returns `false`; else returns `true`. + /// + /// # Example + /// + /// ``` + /// # use vfs::{AbsPathBuf, VfsPath}; + /// let mut path = VfsPath::from(AbsPathBuf::assert("/foo/bar".into())); + /// assert!(path.pop()); + /// assert_eq!(path, VfsPath::from(AbsPathBuf::assert("/foo".into()))); + /// assert!(path.pop()); + /// assert_eq!(path, VfsPath::from(AbsPathBuf::assert("/".into()))); + /// assert!(!path.pop()); + /// ``` pub fn pop(&mut self) -> bool { match &mut self.0 { VfsPathRepr::PathBuf(it) => it.pop(), VfsPathRepr::VirtualPath(it) => it.pop(), } } + + /// Returns `true` if `other` is a prefix of `self`. pub fn starts_with(&self, other: &VfsPath) -> bool { match (&self.0, &other.0) { (VfsPathRepr::PathBuf(lhs), VfsPathRepr::PathBuf(rhs)) => lhs.starts_with(rhs), @@ -48,6 +78,10 @@ impl VfsPath { (VfsPathRepr::VirtualPath(_), _) => false, } } + + /// Returns the `VfsPath` without its final component, if there is one. + /// + /// Returns [`None`] if the path is a root or prefix. pub fn parent(&self) -> Option { let mut parent = self.clone(); if parent.pop() { @@ -57,6 +91,7 @@ impl VfsPath { } } + /// Returns `self`'s base name and file extension. pub fn name_and_extension(&self) -> Option<(&str, Option<&str>)> { match &self.0 { VfsPathRepr::PathBuf(p) => Some(( -- cgit v1.2.3