]> git.lizzy.rs Git - rust.git/blobdiff - crates/ra_lsp_server/src/server_world.rs
automatically collect garbage
[rust.git] / crates / ra_lsp_server / src / server_world.rs
index 12faeb93afe7a2fa2f96de8f5c03aa8100b72dc0..c2167c5d80342c69a2eb283accdfd45f9c4a0eb0 100644 (file)
 use std::{
-    fs,
-    path::{Path, PathBuf},
+    path::PathBuf,
     sync::Arc,
 };
 
-use languageserver_types::Url;
-use ra_analysis::{
-    Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, FileResolver, LibraryData,
+use lsp_types::Url;
+use ra_ide_api::{
+    Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData,
+    SourceRootId
 };
+use ra_vfs::{Vfs, VfsChange, VfsFile, VfsRoot};
 use rustc_hash::FxHashMap;
+use relative_path::RelativePathBuf;
+use parking_lot::RwLock;
+use failure::format_err;
 
 use crate::{
-    path_map::{PathMap, Root},
-    project_model::CargoWorkspace,
-    vfs::{FileEvent, FileEventKind},
+    project_model::{ProjectWorkspace, TargetKind},
     Result,
 };
 
-#[derive(Debug, Default)]
+#[derive(Debug)]
 pub struct ServerWorldState {
-    pub workspaces: Arc<Vec<CargoWorkspace>>,
+    pub roots_to_scan: usize,
+    pub root: PathBuf,
+    pub workspaces: Arc<Vec<ProjectWorkspace>>,
     pub analysis_host: AnalysisHost,
-    pub path_map: PathMap,
-    pub mem_map: FxHashMap<FileId, Option<String>>,
+    pub vfs: Arc<RwLock<Vfs>>,
 }
 
 pub struct ServerWorld {
-    pub workspaces: Arc<Vec<CargoWorkspace>>,
+    pub workspaces: Arc<Vec<ProjectWorkspace>>,
     pub analysis: Analysis,
-    pub path_map: PathMap,
+    pub vfs: Arc<RwLock<Vfs>>,
 }
 
 impl ServerWorldState {
-    pub fn apply_fs_changes(&mut self, events: Vec<FileEvent>) {
+    pub fn new(root: PathBuf, workspaces: Vec<ProjectWorkspace>) -> ServerWorldState {
         let mut change = AnalysisChange::new();
-        let mut inserted = false;
-        {
-            let pm = &mut self.path_map;
-            let mm = &mut self.mem_map;
-            events
-                .into_iter()
-                .map(|event| {
-                    let text = match event.kind {
-                        FileEventKind::Add(text) => text,
-                    };
-                    (event.path, text)
-                })
-                .map(|(path, text)| {
-                    let (ins, file_id) = pm.get_or_insert(path, Root::Workspace);
-                    inserted |= ins;
-                    (file_id, text)
-                })
-                .filter_map(|(file_id, text)| {
-                    if mm.contains_key(&file_id) {
-                        mm.insert(file_id, Some(text));
-                        None
-                    } else {
-                        Some((file_id, text))
-                    }
-                })
-                .for_each(|(file_id, text)| change.add_file(file_id, text));
+
+        let mut roots = Vec::new();
+        roots.push(root.clone());
+        for ws in workspaces.iter() {
+            for pkg in ws.cargo.packages() {
+                roots.push(pkg.root(&ws.cargo).to_path_buf());
+            }
+            for krate in ws.sysroot.crates() {
+                roots.push(krate.root_dir(&ws.sysroot).to_path_buf())
+            }
         }
-        if inserted {
-            change.set_file_resolver(Arc::new(self.path_map.clone()))
+        roots.sort();
+        roots.dedup();
+        let roots_to_scan = roots.len();
+        let (mut vfs, roots) = Vfs::new(roots);
+        for r in roots {
+            let is_local = vfs.root2path(r).starts_with(&root);
+            change.add_root(SourceRootId(r.0.into()), is_local);
         }
-        self.analysis_host.apply_change(change);
-    }
-    pub fn events_to_files(
-        &mut self,
-        events: Vec<FileEvent>,
-    ) -> (Vec<(FileId, String)>, Arc<FileResolver>) {
-        let files = {
-            let pm = &mut self.path_map;
-            events
-                .into_iter()
-                .map(|event| {
-                    let FileEventKind::Add(text) = event.kind;
-                    (event.path, text)
-                })
-                .map(|(path, text)| (pm.get_or_insert(path, Root::Lib).1, text))
-                .collect()
-        };
-        let resolver = Arc::new(self.path_map.clone());
-        (files, resolver)
-    }
-    pub fn add_lib(&mut self, data: LibraryData) {
-        let mut change = AnalysisChange::new();
-        change.add_library(data);
-        self.analysis_host.apply_change(change);
-    }
 
-    pub fn add_mem_file(&mut self, path: PathBuf, text: String) -> FileId {
-        let (inserted, file_id) = self.path_map.get_or_insert(path, Root::Workspace);
-        if self.path_map.get_root(file_id) != Root::Lib {
-            let mut change = AnalysisChange::new();
-            if inserted {
-                change.add_file(file_id, text);
-                change.set_file_resolver(Arc::new(self.path_map.clone()));
-            } else {
-                change.change_file(file_id, text);
+        let mut crate_graph = CrateGraph::default();
+        for ws in workspaces.iter() {
+            // First, load std
+            let mut sysroot_crates = FxHashMap::default();
+            for krate in ws.sysroot.crates() {
+                if let Some(file_id) = vfs.load(krate.root(&ws.sysroot)) {
+                    let file_id = FileId(file_id.0.into());
+                    sysroot_crates.insert(krate, crate_graph.add_crate_root(file_id));
+                }
+            }
+            for from in ws.sysroot.crates() {
+                for to in from.deps(&ws.sysroot) {
+                    let name = to.name(&ws.sysroot);
+                    if let (Some(&from), Some(&to)) =
+                        (sysroot_crates.get(&from), sysroot_crates.get(&to))
+                    {
+                        if let Err(_) = crate_graph.add_dep(from, name.clone(), to) {
+                            log::error!("cyclic dependency between sysroot crates")
+                        }
+                    }
+                }
+            }
+
+            let libstd = ws
+                .sysroot
+                .std()
+                .and_then(|it| sysroot_crates.get(&it).map(|&it| it));
+
+            let mut pkg_to_lib_crate = FxHashMap::default();
+            let mut pkg_crates = FxHashMap::default();
+            // Next, create crates for each package, target pair
+            for pkg in ws.cargo.packages() {
+                let mut lib_tgt = None;
+                for tgt in pkg.targets(&ws.cargo) {
+                    let root = tgt.root(&ws.cargo);
+                    if let Some(file_id) = vfs.load(root) {
+                        let file_id = FileId(file_id.0.into());
+                        let crate_id = crate_graph.add_crate_root(file_id);
+                        if tgt.kind(&ws.cargo) == TargetKind::Lib {
+                            lib_tgt = Some(crate_id);
+                            pkg_to_lib_crate.insert(pkg, crate_id);
+                        }
+                        pkg_crates
+                            .entry(pkg)
+                            .or_insert_with(Vec::new)
+                            .push(crate_id);
+                    }
+                }
+
+                // Set deps to the std and to the lib target of the current package
+                for &from in pkg_crates.get(&pkg).into_iter().flatten() {
+                    if let Some(to) = lib_tgt {
+                        if to != from {
+                            if let Err(_) =
+                                crate_graph.add_dep(from, pkg.name(&ws.cargo).into(), to)
+                            {
+                                log::error!(
+                                    "cyclic dependency between targets of {}",
+                                    pkg.name(&ws.cargo)
+                                )
+                            }
+                        }
+                    }
+                    if let Some(std) = libstd {
+                        if let Err(_) = crate_graph.add_dep(from, "std".into(), std) {
+                            log::error!("cyclic dependency on std for {}", pkg.name(&ws.cargo))
+                        }
+                    }
+                }
+            }
+
+            // Now add a dep ednge from all targets of upstream to the lib
+            // target of downstream.
+            for pkg in ws.cargo.packages() {
+                for dep in pkg.dependencies(&ws.cargo) {
+                    if let Some(&to) = pkg_to_lib_crate.get(&dep.pkg) {
+                        for &from in pkg_crates.get(&pkg).into_iter().flatten() {
+                            if let Err(_) = crate_graph.add_dep(from, dep.name.clone(), to) {
+                                log::error!(
+                                    "cyclic dependency {} -> {}",
+                                    pkg.name(&ws.cargo),
+                                    dep.pkg.name(&ws.cargo)
+                                )
+                            }
+                        }
+                    }
+                }
             }
-            self.analysis_host.apply_change(change);
         }
-        self.mem_map.insert(file_id, None);
-        file_id
-    }
+        change.set_crate_graph(crate_graph);
 
-    pub fn change_mem_file(&mut self, path: &Path, text: String) -> Result<()> {
-        let file_id = self
-            .path_map
-            .get_id(path)
-            .ok_or_else(|| format_err!("change to unknown file: {}", path.display()))?;
-        if self.path_map.get_root(file_id) != Root::Lib {
-            let mut change = AnalysisChange::new();
-            change.change_file(file_id, text);
-            self.analysis_host.apply_change(change);
+        let mut analysis_host = AnalysisHost::default();
+        analysis_host.apply_change(change);
+        ServerWorldState {
+            roots_to_scan,
+            root,
+            workspaces: Arc::new(workspaces),
+            analysis_host,
+            vfs: Arc::new(RwLock::new(vfs)),
         }
-        Ok(())
     }
 
-    pub fn remove_mem_file(&mut self, path: &Path) -> Result<FileId> {
-        let file_id = self
-            .path_map
-            .get_id(path)
-            .ok_or_else(|| format_err!("change to unknown file: {}", path.display()))?;
-        match self.mem_map.remove(&file_id) {
-            Some(_) => (),
-            None => bail!("unmatched close notification"),
-        };
-        // Do this via file watcher ideally.
-        let text = fs::read_to_string(path).ok();
-        if self.path_map.get_root(file_id) != Root::Lib {
-            let mut change = AnalysisChange::new();
-            if let Some(text) = text {
-                change.change_file(file_id, text);
+    /// Returns a vec of libraries
+    /// FIXME: better API here
+    pub fn process_changes(
+        &mut self,
+    ) -> Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)> {
+        let changes = self.vfs.write().commit_changes();
+        if changes.is_empty() {
+            return Vec::new();
+        }
+        let mut libs = Vec::new();
+        let mut change = AnalysisChange::new();
+        for c in changes {
+            match c {
+                VfsChange::AddRoot { root, files } => {
+                    let root_path = self.vfs.read().root2path(root);
+                    if root_path.starts_with(&self.root) {
+                        self.roots_to_scan -= 1;
+                        for (file, path, text) in files {
+                            change.add_file(
+                                SourceRootId(root.0.into()),
+                                FileId(file.0.into()),
+                                path,
+                                text,
+                            );
+                        }
+                    } else {
+                        let files = files
+                            .into_iter()
+                            .map(|(vfsfile, path, text)| (FileId(vfsfile.0.into()), path, text))
+                            .collect();
+                        libs.push((SourceRootId(root.0.into()), files));
+                    }
+                }
+                VfsChange::AddFile {
+                    root,
+                    file,
+                    path,
+                    text,
+                } => {
+                    change.add_file(
+                        SourceRootId(root.0.into()),
+                        FileId(file.0.into()),
+                        path,
+                        text,
+                    );
+                }
+                VfsChange::RemoveFile { root, file, path } => {
+                    change.remove_file(SourceRootId(root.0.into()), FileId(file.0.into()), path)
+                }
+                VfsChange::ChangeFile { file, text } => {
+                    change.change_file(FileId(file.0.into()), text);
+                }
             }
-            self.analysis_host.apply_change(change);
         }
-        Ok(file_id)
+        self.analysis_host.apply_change(change);
+        libs
     }
-    pub fn set_workspaces(&mut self, ws: Vec<CargoWorkspace>) {
-        let mut crate_graph = CrateGraph::default();
-        ws.iter()
-            .flat_map(|ws| {
-                ws.packages()
-                    .flat_map(move |pkg| pkg.targets(ws))
-                    .map(move |tgt| tgt.root(ws))
-            })
-            .for_each(|root| {
-                if let Some(file_id) = self.path_map.get_id(root) {
-                    crate_graph.add_crate_root(file_id);
-                }
-            });
-        self.workspaces = Arc::new(ws);
+
+    pub fn add_lib(&mut self, data: LibraryData) {
+        self.roots_to_scan -= 1;
         let mut change = AnalysisChange::new();
-        change.set_crate_graph(crate_graph);
+        change.add_library(data);
         self.analysis_host.apply_change(change);
     }
+
     pub fn snapshot(&self) -> ServerWorld {
         ServerWorld {
             workspaces: Arc::clone(&self.workspaces),
             analysis: self.analysis_host.analysis(),
-            path_map: self.path_map.clone(),
+            vfs: Arc::clone(&self.vfs),
         }
     }
+
+    pub fn maybe_collect_garbage(&mut self) {
+        self.analysis_host.maybe_collect_garbage()
+    }
+
+    pub fn collect_garbage(&mut self) {
+        self.analysis_host.collect_garbage()
+    }
 }
 
 impl ServerWorld {
@@ -175,15 +250,41 @@ pub fn uri_to_file_id(&self, uri: &Url) -> Result<FileId> {
         let path = uri
             .to_file_path()
             .map_err(|()| format_err!("invalid uri: {}", uri))?;
-        self.path_map
-            .get_id(&path)
-            .ok_or_else(|| format_err!("unknown file: {}", path.display()))
+        let file = self
+            .vfs
+            .read()
+            .path2file(&path)
+            .ok_or_else(|| format_err!("unknown file: {}", path.display()))?;
+        Ok(FileId(file.0.into()))
     }
 
     pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> {
-        let path = self.path_map.get_path(id);
-        let url = Url::from_file_path(path)
-            .map_err(|()| format_err!("can't convert path to url: {}", path.display()))?;
+        let path = self.vfs.read().file2path(VfsFile(id.0.into()));
+        let url = Url::from_file_path(&path)
+            .map_err(|_| format_err!("can't convert path to url: {}", path.display()))?;
         Ok(url)
     }
+
+    pub fn path_to_uri(&self, root: SourceRootId, path: &RelativePathBuf) -> Result<Url> {
+        let base = self.vfs.read().root2path(VfsRoot(root.0.into()));
+        let path = path.to_path(base);
+        let url = Url::from_file_path(&path)
+            .map_err(|_| format_err!("can't convert path to url: {}", path.display()))?;
+        Ok(url)
+    }
+
+    pub fn status(&self) -> String {
+        let mut res = String::new();
+        if self.workspaces.is_empty() {
+            res.push_str("no workspaces\n")
+        } else {
+            res.push_str("workspaces:\n");
+            for w in self.workspaces.iter() {
+                res += &format!("{} packages loaded\n", w.cargo.packages().count());
+            }
+        }
+        res.push_str("\nanalysis:\n");
+        res.push_str(&self.analysis.status());
+        res
+    }
 }