<commit_before>//! VFS stands for Virtual File System.
//!
//! When doing analysis, we don't want to do any IO, we want to keep all source
//! code in memory. However, the actual source code is stored on disk, so you
//! need to get it into the memory in the first place somehow. VFS is the
//! component which does this.
//!
//! It is also responsible for watching the disk for changes, and for merging
//! editor state (modified, unsaved files) with disk state.
//! TODO: Some LSP clients support watching the disk, so this crate should
//! to support custom watcher events (related to https://github.com/rust-analyzer/rust-analyzer/issues/131)
//!
//! VFS is based on a concept of roots: a set of directories on the file system
//! which are watched for changes. Typically, there will be a root for each
//! Cargo package.
mod io;

use std::{
    cmp::Reverse,
    fmt, fs, mem,
    path::{Path, PathBuf},
    sync::Arc,
    thread,
};

use crossbeam_channel::Receiver;
use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap};
use relative_path::{Component, RelativePath, RelativePathBuf};
use rustc_hash::{FxHashMap, FxHashSet};

pub use crate::io::TaskResult as VfsTask;
use io::{TaskResult, Worker};

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VfsRoot(pub RawId);
impl_arena_id!(VfsRoot);

/// Describes the contents of a single source root.
///
/// `RootConfig` 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
pub(crate) struct RootConfig {
    root: PathBuf,
    excluded_dirs: Vec<PathBuf>,
}

pub(crate) struct Roots {
    roots: Arena<VfsRoot, Arc<RootConfig>>,
}

impl std::ops::Deref for Roots {
    type Target = Arena<VfsRoot, Arc<RootConfig>>;
    fn deref(&self) -> &Self::Target {
        &self.roots
    }
}

impl RootConfig {
    fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootConfig {
        RootConfig { root, excluded_dirs }
    }
    /// Checks if root contains a path and returns a root-relative path.
    pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> {
        // First, check excluded dirs
        if self.excluded_dirs.iter().any(|it| path.starts_with(it)) {
            return None;
        }
        let rel_path = path.strip_prefix(&self.root).ok()?;
        let rel_path = RelativePathBuf::from_path(rel_path).ok()?;

        // Ignore some common directories.
        //
        // FIXME: don't hard-code, specify at source-root creation time using
        // gitignore
        for (i, c) in rel_path.components().enumerate() {
            if let Component::Normal(c) = c {
                if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
                    return None;
                }
            }
        }

        if path.is_file() && rel_path.extension() != Some("rs") {
            return None;
        }

        Some(rel_path)
    }
}

impl Roots {
    pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
        let mut roots = Arena::default();<commit_msg>In `RootConfig::contains`, check against canonicalized version of root path since OS may hand us data that uses that rather than the root as specified by the user.<commit_after>//! VFS stands for Virtual File System.
//!
//! When doing analysis, we don't want to do any IO, we want to keep all source
//! code in memory. However, the actual source code is stored on disk, so you
//! need to get it into the memory in the first place somehow. VFS is the
//! component which does this.
//!
//! It is also responsible for watching the disk for changes, and for merging
//! editor state (modified, unsaved files) with disk state.
//! TODO: Some LSP clients support watching the disk, so this crate should
//! to support custom watcher events (related to https://github.com/rust-analyzer/rust-analyzer/issues/131)
//!
//! VFS is based on a concept of roots: a set of directories on the file system
//! which are watched for changes. Typically, there will be a root for each
//! Cargo package.
mod io;

use std::{
    cmp::Reverse,
    fmt, fs, mem,
    path::{Path, PathBuf},
    sync::Arc,
    thread,
};

use crossbeam_channel::Receiver;
use ra_arena::{impl_arena_id, Arena, RawId, map::ArenaMap};
use relative_path::{Component, RelativePath, RelativePathBuf};
use rustc_hash::{FxHashMap, FxHashSet};

pub use crate::io::TaskResult as VfsTask;
use io::{TaskResult, Worker};

#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct VfsRoot(pub RawId);
impl_arena_id!(VfsRoot);

/// Describes the contents of a single source root.
///
/// `RootConfig` 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
pub(crate) struct RootConfig {
    root: PathBuf,
    // result of `root.canonicalize()` if that differs from `root`; `None` otherwise.
    canonical_root: Option<PathBuf>,
    excluded_dirs: Vec<PathBuf>,
}

pub(crate) struct Roots {
    roots: Arena<VfsRoot, Arc<RootConfig>>,
}

impl std::ops::Deref for Roots {
    type Target = Arena<VfsRoot, Arc<RootConfig>>;
    fn deref(&self) -> &Self::Target {
        &self.roots
    }
}

impl RootConfig {
    fn new(root: PathBuf, excluded_dirs: Vec<PathBuf>) -> RootConfig {
        let mut canonical_root = root.canonicalize().ok();
        if Some(&root) == canonical_root.as_ref() {
            canonical_root = None;
        }
        RootConfig { root, canonical_root, excluded_dirs }
    }
    /// Checks if root contains a path and returns a root-relative path.
    pub(crate) fn contains(&self, path: &Path) -> Option<RelativePathBuf> {
        // First, check excluded dirs
        if self.excluded_dirs.iter().any(|it| path.starts_with(it)) {
            return None;
        }
        let rel_path = path.strip_prefix(&self.root)
            .or_else(|err_payload| {
                self.canonical_root
                    .as_ref()
                    .map_or(Err(err_payload),
                            |canonical_root| path.strip_prefix(canonical_root))
            })
            .ok()?;
        let rel_path = RelativePathBuf::from_path(rel_path).ok()?;

        // Ignore some common directories.
        //
        // FIXME: don't hard-code, specify at source-root creation time using
        // gitignore
        for (i, c) in rel_path.components().enumerate() {
            if let Component::Normal(c) = c {
                if (i == 0 && c == "target") || c == ".git" || c == "node_modules" {
                    return None;
                }
            }
        }

        if path.is_file() && rel_path.extension() != Some("rs") {
            return None;
        }

        Some(rel_path)
    }
}

impl Roots {
    pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
        let mut roots = Arena::default();
{'fraction_non_alphanumeric': 0.12396221013455483, 'fraction_numerical': 0.001145147437732608, 'mean_word_length': 2.694870438921206, 'pattern_counts': {'":': 0, '<': 20, '<?xml version=': 0, '>': 28, 'https://': 2, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '1745804', 'n_tokens_mistral': 2144, 'n_tokens_neox': 2089, 'n_words': 730}