summaryrefslogtreecommitdiffstats
path: root/src/tools/rust-analyzer/crates/vfs/src/loader.rs
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:02:58 +0000
commit698f8c2f01ea549d77d7dc3338a12e04c11057b9 (patch)
tree173a775858bd501c378080a10dca74132f05bc50 /src/tools/rust-analyzer/crates/vfs/src/loader.rs
parentInitial commit. (diff)
downloadrustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.tar.xz
rustc-698f8c2f01ea549d77d7dc3338a12e04c11057b9.zip
Adding upstream version 1.64.0+dfsg1.upstream/1.64.0+dfsg1
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/rust-analyzer/crates/vfs/src/loader.rs')
-rw-r--r--src/tools/rust-analyzer/crates/vfs/src/loader.rs215
1 files changed, 215 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/vfs/src/loader.rs b/src/tools/rust-analyzer/crates/vfs/src/loader.rs
new file mode 100644
index 000000000..e2d74782a
--- /dev/null
+++ b/src/tools/rust-analyzer/crates/vfs/src/loader.rs
@@ -0,0 +1,215 @@
+//! Object safe interface for file watching and reading.
+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<AbsPathBuf>),
+ /// The `Entry` is represented by `Directories`.
+ 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.
+///
+/// If a path is in both `include` and `exclude`, the `exclude` one wins.
+#[derive(Debug, Clone, Default)]
+pub struct Directories {
+ pub extensions: Vec<String>,
+ pub include: Vec<AbsPathBuf>,
+ pub exclude: Vec<AbsPathBuf>,
+}
+
+/// [`Handle`]'s configuration.
+#[derive(Debug)]
+pub struct Config {
+ /// Version number to associate progress updates to the right config
+ /// version.
+ pub version: u32,
+ /// Set of initially loaded files.
+ pub load: Vec<Entry>,
+ /// Index of watched entries in `load`.
+ ///
+ /// If a path in a watched entry is modified,the [`Handle`] should notify it.
+ pub watch: Vec<usize>,
+}
+
+/// 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, config_version: u32 },
+ /// The handle loaded the following files' content.
+ Loaded { files: Vec<(AbsPathBuf, Option<Vec<u8>>)> },
+}
+
+/// Type that will receive [`Messages`](Message) from a [`Handle`].
+pub type Sender = Box<dyn Fn(Message) + Send>;
+
+/// 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<Vec<u8>>;
+}
+
+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,
+ Entry::Directories(dirs) => dirs.contains_dir(path),
+ }
+ }
+}
+
+impl Directories {
+ /// Returns `true` if `path` is included in `self`.
+ pub fn contains_file(&self, path: &AbsPath) -> bool {
+ // First, check the file extension...
+ let ext = path.extension().unwrap_or_default();
+ if self.extensions.iter().all(|it| it.as_str() != ext) {
+ return false;
+ }
+
+ // Then, check for path inclusion...
+ 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)
+ }
+
+ /// Returns `true` if `path` is included in `self`.
+ ///
+ /// It is included if
+ /// - An element in `self.include` is a prefix of `path`.
+ /// - This path is longer than any element in `self.exclude` that is a prefix
+ /// of `path`. In case of equality, exclusion wins.
+ fn includes_path(&self, path: &AbsPath) -> bool {
+ let mut include: Option<&AbsPathBuf> = None;
+ for incl in &self.include {
+ if path.starts_with(incl) {
+ include = Some(match include {
+ Some(prev) if prev.starts_with(incl) => prev,
+ _ => incl,
+ });
+ }
+ }
+
+ let include = match include {
+ Some(it) => it,
+ None => return false,
+ };
+
+ !self.exclude.iter().any(|excl| path.starts_with(excl) && excl.starts_with(include))
+ }
+}
+
+/// Returns :
+/// ```text
+/// Directories {
+/// extensions: ["rs"],
+/// include: [base],
+/// exclude: [base/<exclude>],
+/// }
+/// ```
+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 {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Message::Loaded { files } => {
+ f.debug_struct("Loaded").field("n_files", &files.len()).finish()
+ }
+ Message::Progress { n_total, n_done, config_version } => f
+ .debug_struct("Progress")
+ .field("n_total", n_total)
+ .field("n_done", n_done)
+ .field("config_version", config_version)
+ .finish(),
+ }
+ }
+}
+
+#[test]
+fn handle_is_object_safe() {
+ fn _assert(_: &dyn Handle) {}
+}