]> git.lizzy.rs Git - rust.git/blobdiff - crates/ra_vfs/src/lib.rs
move public API to top of the file
[rust.git] / crates / ra_vfs / src / lib.rs
index 3805be570a694d5a0538adacf8f17490d358051c..8005c4ff8ae28e3bad52c5fc75061d181537985c 100644 (file)
 //!
 //! 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)
+//!
+//! 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 roots;
 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 relative_path::{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` whihc
-/// specifes the source root or as a function whihc 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 }
-    }
-    /// Cheks 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)
-    }
-}
+use crate::{
+    io::{TaskResult, Worker},
+    roots::Roots,
+};
 
-impl Roots {
-    pub(crate) fn new(mut paths: Vec<PathBuf>) -> Roots {
-        let mut roots = Arena::default();
-        // A hack to make nesting work.
-        paths.sort_by_key(|it| Reverse(it.as_os_str().len()));
-        paths.dedup();
-        for (i, path) in paths.iter().enumerate() {
-            let nested_roots = paths[..i]
-                .iter()
-                .filter(|it| it.starts_with(path))
-                .map(|it| it.clone())
-                .collect::<Vec<_>>();
-
-            let config = Arc::new(RootConfig::new(path.clone(), nested_roots));
-
-            roots.alloc(config.clone());
-        }
-        Roots { roots }
-    }
-    pub(crate) fn find(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf)> {
-        self.roots.iter().find_map(|(root, data)| data.contains(path).map(|it| (root, it)))
-    }
-}
+pub use crate::{
+    io::TaskResult as VfsTask,
+    roots::VfsRoot,
+};
 
 #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
-pub struct VfsFile(pub RawId);
-impl_arena_id!(VfsFile);
+pub struct VfsFile(pub u32);
 
 struct VfsFileData {
     root: VfsRoot,
@@ -126,8 +50,8 @@ struct VfsFileData {
 
 pub struct Vfs {
     roots: Arc<Roots>,
-    files: Arena<VfsFile, VfsFileData>,
-    root2files: ArenaMap<VfsRoot, FxHashSet<VfsFile>>,
+    files: Vec<VfsFileData>,
+    root2files: FxHashMap<VfsRoot, FxHashSet<VfsFile>>,
     pending_changes: Vec<VfsChange>,
     worker: Worker,
 }
@@ -142,24 +66,31 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     }
 }
 
+#[derive(Debug, Clone)]
+pub enum VfsChange {
+    AddRoot { root: VfsRoot, files: Vec<(VfsFile, RelativePathBuf, Arc<String>)> },
+    AddFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf, text: Arc<String> },
+    RemoveFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf },
+    ChangeFile { file: VfsFile, text: Arc<String> },
+}
+
 impl Vfs {
     pub fn new(roots: Vec<PathBuf>) -> (Vfs, Vec<VfsRoot>) {
         let roots = Arc::new(Roots::new(roots));
-        let worker = io::Worker::start(Arc::clone(&roots));
-        let mut root2files = ArenaMap::default();
+        let worker = io::start(Arc::clone(&roots));
+        let mut root2files = FxHashMap::default();
 
-        for (root, config) in roots.iter() {
+        for root in roots.iter() {
             root2files.insert(root, Default::default());
-            worker.sender().send(io::Task::AddRoot { root, config: Arc::clone(config) }).unwrap();
+            worker.sender().send(io::Task::AddRoot { root }).unwrap();
         }
-        let res =
-            Vfs { roots, files: Arena::default(), root2files, worker, pending_changes: Vec::new() };
-        let vfs_roots = res.roots.iter().map(|(id, _)| id).collect();
+        let res = Vfs { roots, files: Vec::new(), root2files, worker, pending_changes: Vec::new() };
+        let vfs_roots = res.roots.iter().collect();
         (res, vfs_roots)
     }
 
     pub fn root2path(&self, root: VfsRoot) -> PathBuf {
-        self.roots[root].root.clone()
+        self.roots.path(root).to_path_buf()
     }
 
     pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
@@ -170,19 +101,12 @@ pub fn path2file(&self, path: &Path) -> Option<VfsFile> {
     }
 
     pub fn file2path(&self, file: VfsFile) -> PathBuf {
-        let rel_path = &self.files[file].path;
-        let root_path = &self.roots[self.files[file].root].root;
+        let rel_path = &self.file(file).path;
+        let root_path = &self.roots.path(self.file(file).root);
         rel_path.to_path(root_path)
     }
 
-    pub fn file_for_path(&self, path: &Path) -> Option<VfsFile> {
-        if let Some((_root, _path, Some(file))) = self.find_root(path) {
-            return Some(file);
-        }
-        None
-    }
-
-    pub fn num_roots(&self) -> usize {
+    pub fn n_roots(&self) -> usize {
         self.roots.len()
     }
 
@@ -193,7 +117,7 @@ pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
             } else {
                 let text = fs::read_to_string(path).unwrap_or_default();
                 let text = Arc::new(text);
-                let file = self.add_file(root, rel_path.clone(), Arc::clone(&text), false);
+                let file = self.raw_add_file(root, rel_path.clone(), Arc::clone(&text), false);
                 let change = VfsChange::AddFile { file, text, root, path: rel_path };
                 self.pending_changes.push(change);
                 Some(file)
@@ -202,6 +126,39 @@ pub fn load(&mut self, path: &Path) -> Option<VfsFile> {
         None
     }
 
+    pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
+        let (root, rel_path, file) = self.find_root(path)?;
+        if let Some(file) = file {
+            self.change_file_event(file, text, true);
+            Some(file)
+        } else {
+            self.add_file_event(root, rel_path, text, true)
+        }
+    }
+
+    pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
+        if let Some((_root, _path, file)) = self.find_root(path) {
+            let file = file.expect("can't change a file which wasn't added");
+            self.change_file_event(file, new_text, true);
+        }
+    }
+
+    pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
+        let (root, rel_path, file) = self.find_root(path)?;
+        let file = file.expect("can't remove a file which wasn't added");
+        let full_path = rel_path.to_path(&self.roots.path(root));
+        if let Ok(text) = fs::read_to_string(&full_path) {
+            self.change_file_event(file, text, false);
+        } else {
+            self.remove_file_event(root, rel_path, file);
+        }
+        Some(file)
+    }
+
+    pub fn commit_changes(&mut self) -> Vec<VfsChange> {
+        mem::replace(&mut self.pending_changes, Vec::new())
+    }
+
     pub fn task_receiver(&self) -> &Receiver<io::TaskResult> {
         self.worker.receiver()
     }
@@ -210,47 +167,51 @@ pub fn handle_task(&mut self, task: io::TaskResult) {
         match task {
             TaskResult::BulkLoadRoot { root, files } => {
                 let mut cur_files = Vec::new();
-                // While we were scanning the root in the backgound, a file might have
+                // While we were scanning the root in the background, a file might have
                 // been open in the editor, so we need to account for that.
-                let exising = self.root2files[root]
+                let existing = self.root2files[&root]
                     .iter()
-                    .map(|&file| (self.files[file].path.clone(), file))
+                    .map(|&file| (self.file(file).path.clone(), file))
                     .collect::<FxHashMap<_, _>>();
                 for (path, text) in files {
-                    if let Some(&file) = exising.get(&path) {
-                        let text = Arc::clone(&self.files[file].text);
+                    if let Some(&file) = existing.get(&path) {
+                        let text = Arc::clone(&self.file(file).text);
                         cur_files.push((file, path, text));
                         continue;
                     }
                     let text = Arc::new(text);
-                    let file = self.add_file(root, path.clone(), Arc::clone(&text), false);
+                    let file = self.raw_add_file(root, path.clone(), Arc::clone(&text), false);
                     cur_files.push((file, path, text));
                 }
 
                 let change = VfsChange::AddRoot { root, files: cur_files };
                 self.pending_changes.push(change);
             }
-            TaskResult::AddSingleFile { root, path, text } => {
-                if self.find_file(root, &path).is_none() {
-                    self.do_add_file(root, path, text, false);
-                }
-            }
-            TaskResult::ChangeSingleFile { root, path, text } => {
-                if let Some(file) = self.find_file(root, &path) {
-                    self.do_change_file(file, text, false);
-                } else {
-                    self.do_add_file(root, path, text, false);
+            TaskResult::SingleFile { root, path, text } => {
+                let existing_file = self.find_file(root, &path);
+                if existing_file.map(|file| self.file(file).is_overlayed) == Some(true) {
+                    return;
                 }
-            }
-            TaskResult::RemoveSingleFile { root, path } => {
-                if let Some(file) = self.find_file(root, &path) {
-                    self.do_remove_file(root, path, file, false);
+                match (existing_file, text) {
+                    (Some(file), None) => {
+                        self.remove_file_event(root, path, file);
+                    }
+                    (None, Some(text)) => {
+                        self.add_file_event(root, path, text, false);
+                    }
+                    (Some(file), Some(text)) => {
+                        self.change_file_event(file, text, false);
+                    }
+                    (None, None) => (),
                 }
             }
         }
     }
 
-    fn do_add_file(
+    // *_event calls change the state of VFS and push a change onto pending
+    // changes array.
+
+    fn add_file_event(
         &mut self,
         root: VfsRoot,
         path: RelativePathBuf,
@@ -258,79 +219,25 @@ fn do_add_file(
         is_overlay: bool,
     ) -> Option<VfsFile> {
         let text = Arc::new(text);
-        let file = self.add_file(root, path.clone(), text.clone(), is_overlay);
+        let file = self.raw_add_file(root, path.clone(), text.clone(), is_overlay);
         self.pending_changes.push(VfsChange::AddFile { file, root, path, text });
         Some(file)
     }
 
-    fn do_change_file(&mut self, file: VfsFile, text: String, is_overlay: bool) {
-        if !is_overlay && self.files[file].is_overlayed {
-            return;
-        }
+    fn change_file_event(&mut self, file: VfsFile, text: String, is_overlay: bool) {
         let text = Arc::new(text);
-        self.change_file(file, text.clone(), is_overlay);
+        self.raw_change_file(file, text.clone(), is_overlay);
         self.pending_changes.push(VfsChange::ChangeFile { file, text });
     }
 
-    fn do_remove_file(
-        &mut self,
-        root: VfsRoot,
-        path: RelativePathBuf,
-        file: VfsFile,
-        is_overlay: bool,
-    ) {
-        if !is_overlay && self.files[file].is_overlayed {
-            return;
-        }
-        self.remove_file(file);
+    fn remove_file_event(&mut self, root: VfsRoot, path: RelativePathBuf, file: VfsFile) {
+        self.raw_remove_file(file);
         self.pending_changes.push(VfsChange::RemoveFile { root, path, file });
     }
 
-    pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option<VfsFile> {
-        if let Some((root, rel_path, file)) = self.find_root(path) {
-            if let Some(file) = file {
-                self.do_change_file(file, text, true);
-                Some(file)
-            } else {
-                self.do_add_file(root, rel_path, text, true)
-            }
-        } else {
-            None
-        }
-    }
-
-    pub fn change_file_overlay(&mut self, path: &Path, new_text: String) {
-        if let Some((_root, _path, file)) = self.find_root(path) {
-            let file = file.expect("can't change a file which wasn't added");
-            self.do_change_file(file, new_text, true);
-        }
-    }
-
-    pub fn remove_file_overlay(&mut self, path: &Path) -> Option<VfsFile> {
-        if let Some((root, path, file)) = self.find_root(path) {
-            let file = file.expect("can't remove a file which wasn't added");
-            let full_path = path.to_path(&self.roots[root].root);
-            if let Ok(text) = fs::read_to_string(&full_path) {
-                self.do_change_file(file, text, true);
-            } else {
-                self.do_remove_file(root, path, file, true);
-            }
-            Some(file)
-        } else {
-            None
-        }
-    }
-
-    pub fn commit_changes(&mut self) -> Vec<VfsChange> {
-        mem::replace(&mut self.pending_changes, Vec::new())
-    }
-
-    /// Sutdown the VFS and terminate the background watching thread.
-    pub fn shutdown(self) -> thread::Result<()> {
-        self.worker.shutdown()
-    }
+    // raw_* calls change the state of VFS, but **do not** emit events.
 
-    fn add_file(
+    fn raw_add_file(
         &mut self,
         root: VfsRoot,
         path: RelativePathBuf,
@@ -338,23 +245,24 @@ fn add_file(
         is_overlayed: bool,
     ) -> VfsFile {
         let data = VfsFileData { root, path, text, is_overlayed };
-        let file = self.files.alloc(data);
-        self.root2files.get_mut(root).unwrap().insert(file);
+        let file = VfsFile(self.files.len() as u32);
+        self.files.push(data);
+        self.root2files.get_mut(&root).unwrap().insert(file);
         file
     }
 
-    fn change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
-        let mut file_data = &mut self.files[file];
+    fn raw_change_file(&mut self, file: VfsFile, new_text: Arc<String>, is_overlayed: bool) {
+        let mut file_data = &mut self.file_mut(file);
         file_data.text = new_text;
         file_data.is_overlayed = is_overlayed;
     }
 
-    fn remove_file(&mut self, file: VfsFile) {
-        //FIXME: use arena with removal
-        self.files[file].text = Default::default();
-        self.files[file].path = Default::default();
-        let root = self.files[file].root;
-        let removed = self.root2files.get_mut(root).unwrap().remove(&file);
+    fn raw_remove_file(&mut self, file: VfsFile) {
+        // FIXME: use arena with removal
+        self.file_mut(file).text = Default::default();
+        self.file_mut(file).path = Default::default();
+        let root = self.file(file).root;
+        let removed = self.root2files.get_mut(&root).unwrap().remove(&file);
         assert!(removed);
     }
 
@@ -365,14 +273,14 @@ fn find_root(&self, path: &Path) -> Option<(VfsRoot, RelativePathBuf, Option<Vfs
     }
 
     fn find_file(&self, root: VfsRoot, path: &RelativePath) -> Option<VfsFile> {
-        self.root2files[root].iter().map(|&it| it).find(|&file| self.files[file].path == path)
+        self.root2files[&root].iter().map(|&it| it).find(|&file| self.file(file).path == path)
     }
-}
 
-#[derive(Debug, Clone)]
-pub enum VfsChange {
-    AddRoot { root: VfsRoot, files: Vec<(VfsFile, RelativePathBuf, Arc<String>)> },
-    AddFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf, text: Arc<String> },
-    RemoveFile { root: VfsRoot, file: VfsFile, path: RelativePathBuf },
-    ChangeFile { file: VfsFile, text: Arc<String> },
+    fn file(&self, file: VfsFile) -> &VfsFileData {
+        &self.files[file.0 as usize]
+    }
+
+    fn file_mut(&mut self, file: VfsFile) -> &mut VfsFileData {
+        &mut self.files[file.0 as usize]
+    }
 }