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::new();
- 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 {
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
+ }
}