//!
//! Each tick provides an immutable snapshot of the state as `WorldSnapshot`.
-use std::{
- path::{Path, PathBuf},
- sync::Arc,
-};
+use std::{convert::TryFrom, sync::Arc};
use crossbeam_channel::{unbounded, Receiver};
use lsp_types::Url;
use parking_lot::RwLock;
+use ra_db::{CrateId, SourceRoot, VfsPath};
use ra_flycheck::{Flycheck, FlycheckConfig};
-use ra_ide::{
- Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, SourceRootId,
-};
-use ra_project_model::{ProcMacroClient, ProjectWorkspace};
-use ra_vfs::{LineEndings, RootEntry, Vfs, VfsChange, VfsFile, VfsRoot, VfsTask, Watch};
-use relative_path::RelativePathBuf;
+use ra_ide::{Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId};
+use ra_project_model::{CargoWorkspace, ProcMacroClient, ProjectWorkspace, Target};
use stdx::format_to;
+use vfs::{file_set::FileSetConfig, loader::Handle, AbsPath, AbsPathBuf};
use crate::{
- config::Config,
- diagnostics::{
- to_proto::url_from_path_with_drive_lowercasing, CheckFixes, DiagnosticCollection,
- },
- main_loop::pending_requests::{CompletedRequest, LatestRequests},
- vfs_glob::{Glob, RustPackageFilterBuilder},
- LspError, Result,
+ config::{Config, FilesWatcher},
+ diagnostics::{CheckFixes, DiagnosticCollection},
+ from_proto,
+ line_endings::LineEndings,
+ main_loop::ReqQueue,
+ request_metrics::{LatestRequests, RequestMetrics},
+ to_proto::url_from_abs_path,
+ Result,
};
-use ra_db::ExternSourceId;
use rustc_hash::{FxHashMap, FxHashSet};
-fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) -> Option<Flycheck> {
+fn create_flycheck(
+ workspaces: &[ProjectWorkspace],
+ config: &FlycheckConfig,
+ progress_src: &ProgressSource<(), String>,
+) -> Option<Flycheck> {
// FIXME: Figure out the multi-workspace situation
- workspaces
- .iter()
- .find_map(|w| match w {
- ProjectWorkspace::Cargo { cargo, .. } => Some(cargo),
- ProjectWorkspace::Json { .. } => None,
- })
- .map(|cargo| {
+ workspaces.iter().find_map(move |w| match w {
+ ProjectWorkspace::Cargo { cargo, .. } => {
let cargo_project_root = cargo.workspace_root().to_path_buf();
- Some(Flycheck::new(config.clone(), cargo_project_root))
- })
- .unwrap_or_else(|| {
+ Some(Flycheck::new(config.clone(), cargo_project_root.into()))
+ }
+ ProjectWorkspace::Json { .. } => {
log::warn!("Cargo check watching only supported for cargo workspaces, disabling");
None
- })
+ }
+ })
+}
+
+#[derive(Eq, PartialEq)]
+pub(crate) enum Status {
+ Loading,
+ Ready,
+}
+
+impl Default for Status {
+ fn default() -> Self {
+ Status::Loading
+ }
}
/// `GlobalState` is the primary mutable state of the language server
/// The most interesting components are `vfs`, which stores a consistent
/// snapshot of the file systems, and `analysis_host`, which stores our
/// incremental salsa database.
-#[derive(Debug)]
-pub struct GlobalState {
- pub config: Config,
- pub local_roots: Vec<PathBuf>,
- pub workspaces: Arc<Vec<ProjectWorkspace>>,
- pub analysis_host: AnalysisHost,
- pub vfs: Arc<RwLock<Vfs>>,
- pub task_receiver: Receiver<VfsTask>,
- pub latest_requests: Arc<RwLock<LatestRequests>>,
- pub flycheck: Option<Flycheck>,
- pub diagnostics: DiagnosticCollection,
- pub proc_macro_client: ProcMacroClient,
+pub(crate) struct GlobalState {
+ pub(crate) config: Config,
+ pub(crate) analysis_host: AnalysisHost,
+ pub(crate) loader: Box<dyn vfs::loader::Handle>,
+ pub(crate) task_receiver: Receiver<vfs::loader::Message>,
+ pub(crate) flycheck: Option<Flycheck>,
+ pub(crate) diagnostics: DiagnosticCollection,
+ pub(crate) mem_docs: FxHashSet<VfsPath>,
+ pub(crate) vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
+ pub(crate) status: Status,
+ pub(crate) req_queue: ReqQueue,
+ latest_requests: Arc<RwLock<LatestRequests>>,
+ source_root_config: SourceRootConfig,
+ _proc_macro_client: ProcMacroClient,
+ workspaces: Arc<Vec<ProjectWorkspace>>,
}
/// An immutable snapshot of the world's state at a point in time.
-pub struct GlobalStateSnapshot {
- pub config: Config,
- pub workspaces: Arc<Vec<ProjectWorkspace>>,
- pub analysis: Analysis,
- pub latest_requests: Arc<RwLock<LatestRequests>>,
- pub check_fixes: CheckFixes,
- vfs: Arc<RwLock<Vfs>>,
+pub(crate) struct GlobalStateSnapshot {
+ pub(crate) config: Config,
+ pub(crate) analysis: Analysis,
+ pub(crate) check_fixes: CheckFixes,
+ pub(crate) latest_requests: Arc<RwLock<LatestRequests>>,
+ vfs: Arc<RwLock<(vfs::Vfs, FxHashMap<FileId, LineEndings>)>>,
+ workspaces: Arc<Vec<ProjectWorkspace>>,
}
impl GlobalState {
- pub fn new(
+ pub(crate) fn new(
workspaces: Vec<ProjectWorkspace>,
lru_capacity: Option<usize>,
- exclude_globs: &[Glob],
- watch: Watch,
config: Config,
+ req_queue: ReqQueue,
) -> GlobalState {
let mut change = AnalysisChange::new();
- let mut extern_dirs: FxHashSet<PathBuf> = FxHashSet::default();
-
- let mut local_roots = Vec::new();
- let roots: Vec<_> = {
- let create_filter = |is_member| {
- RustPackageFilterBuilder::default()
- .set_member(is_member)
- .exclude(exclude_globs.iter().cloned())
- .into_vfs_filter()
- };
- let mut roots = Vec::new();
- for root in workspaces.iter().flat_map(ProjectWorkspace::to_roots) {
- let path = root.path().to_owned();
- if root.is_member() {
- local_roots.push(path.clone());
- }
- roots.push(RootEntry::new(path, create_filter(root.is_member())));
- if let Some(out_dir) = root.out_dir() {
- extern_dirs.insert(out_dir.to_path_buf());
- roots.push(RootEntry::new(
- out_dir.to_path_buf(),
- create_filter(root.is_member()),
- ))
- }
- }
- roots
- };
-
- let (task_sender, task_receiver) = unbounded();
- let task_sender = Box::new(move |t| task_sender.send(t).unwrap());
- let (mut vfs, vfs_roots) = Vfs::new(roots, task_sender, watch);
+ let project_folders = ProjectFolders::new(&workspaces);
- let mut extern_source_roots = FxHashMap::default();
- for r in vfs_roots {
- let vfs_root_path = vfs.root2path(r);
- let is_local = local_roots.iter().any(|it| vfs_root_path.starts_with(it));
- change.add_root(SourceRootId(r.0), is_local);
- change.set_debug_root_path(SourceRootId(r.0), vfs_root_path.display().to_string());
-
- // FIXME: add path2root in vfs to simpily this logic
- if extern_dirs.contains(&vfs_root_path) {
- extern_source_roots.insert(vfs_root_path, ExternSourceId(r.0));
- }
- }
+ let (task_sender, task_receiver) = unbounded::<vfs::loader::Message>();
+ let mut vfs = vfs::Vfs::default();
let proc_macro_client = match &config.proc_macro_srv {
None => ProcMacroClient::dummy(),
},
};
+ let mut loader = {
+ let loader = vfs_notify::LoaderHandle::spawn(Box::new(move |msg| {
+ task_sender.send(msg).unwrap()
+ }));
+ Box::new(loader)
+ };
+ let watch = match config.files.watcher {
+ FilesWatcher::Client => vec![],
+ FilesWatcher::Notify => project_folders.watch,
+ };
+ loader.set_config(vfs::loader::Config { load: project_folders.load, watch });
+
// Create crate graph from all the workspaces
let mut crate_graph = CrateGraph::default();
- let mut load = |path: &Path| {
- // Some path from metadata will be non canonicalized, e.g. /foo/../bar/lib.rs
- let path = path.canonicalize().ok()?;
- let vfs_file = vfs.load(&path);
- vfs_file.map(|f| FileId(f.0))
+ let mut load = |path: &AbsPath| {
+ let contents = loader.load_sync(path);
+ let path = vfs::VfsPath::from(path.to_path_buf());
+ vfs.set_file_contents(path.clone(), contents);
+ vfs.file_id(&path)
};
for ws in workspaces.iter() {
crate_graph.extend(ws.to_crate_graph(
config.cargo.target.as_deref(),
- &extern_source_roots,
&proc_macro_client,
&mut load,
));
}
change.set_crate_graph(crate_graph);
- let flycheck = config.check.as_ref().and_then(|c| create_flycheck(&workspaces, c));
+ let (flycheck_progress_receiver, flycheck_progress_src) =
+ ProgressSource::real_if(config.client_caps.work_done_progress);
+ let flycheck = config
+ .check
+ .as_ref()
+ .and_then(|c| create_flycheck(&workspaces, c, &flycheck_progress_src));
let mut analysis_host = AnalysisHost::new(lru_capacity);
analysis_host.apply_change(change);
- GlobalState {
+ let mut res = GlobalState {
config,
- local_roots,
- workspaces: Arc::new(workspaces),
analysis_host,
- vfs: Arc::new(RwLock::new(vfs)),
+ loader,
task_receiver,
- latest_requests: Default::default(),
flycheck,
+ flycheck_progress_src,
+ flycheck_progress_receiver,
diagnostics: Default::default(),
- proc_macro_client,
- }
+ mem_docs: FxHashSet::default(),
+ vfs: Arc::new(RwLock::new((vfs, FxHashMap::default()))),
+ status: Status::default(),
+ req_queue,
+ latest_requests: Default::default(),
+ source_root_config: project_folders.source_root_config,
+ _proc_macro_client: proc_macro_client,
+ workspaces: Arc::new(workspaces),
+ };
+ res.process_changes();
+ res
}
- pub fn update_configuration(&mut self, config: Config) {
+ pub(crate) fn update_configuration(&mut self, config: Config) {
self.analysis_host.update_lru_capacity(config.lru_capacity);
if config.check != self.config.check {
- self.flycheck =
- config.check.as_ref().and_then(|it| create_flycheck(&self.workspaces, it));
+ self.flycheck = config
+ .check
+ .as_ref()
+ .and_then(|it| create_flycheck(&self.workspaces, it, &self.flycheck_progress_src));
}
self.config = config;
}
- /// Returns a vec of libraries
- /// FIXME: better API here
- pub fn process_changes(
- &mut self,
- roots_scanned: &mut usize,
- ) -> Option<Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc<String>)>)>> {
- let changes = self.vfs.write().commit_changes();
- if changes.is_empty() {
- return None;
- }
- 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);
- let is_local = self.local_roots.iter().any(|r| root_path.starts_with(r));
- if is_local {
- *roots_scanned += 1;
- for (file, path, text) in files {
- change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
+ pub(crate) fn process_changes(&mut self) -> bool {
+ let change = {
+ let mut change = AnalysisChange::new();
+ let (vfs, line_endings_map) = &mut *self.vfs.write();
+ let changed_files = vfs.take_changes();
+ if changed_files.is_empty() {
+ return false;
+ }
+
+ let fs_op = changed_files.iter().any(|it| it.is_created_or_deleted());
+ if fs_op {
+ let roots = self.source_root_config.partition(&vfs);
+ change.set_roots(roots)
+ }
+
+ for file in changed_files {
+ let text = if file.exists() {
+ let bytes = vfs.file_contents(file.file_id).to_vec();
+ match String::from_utf8(bytes).ok() {
+ Some(text) => {
+ let (text, line_endings) = LineEndings::normalize(text);
+ line_endings_map.insert(file.file_id, line_endings);
+ Some(Arc::new(text))
}
- } else {
- let files = files
- .into_iter()
- .map(|(vfsfile, path, text)| (FileId(vfsfile.0), path, text))
- .collect();
- libs.push((SourceRootId(root.0), files));
+ None => None,
}
- }
- VfsChange::AddFile { root, file, path, text } => {
- change.add_file(SourceRootId(root.0), FileId(file.0), path, text);
- }
- VfsChange::RemoveFile { root, file, path } => {
- change.remove_file(SourceRootId(root.0), FileId(file.0), path)
- }
- VfsChange::ChangeFile { file, text } => {
- change.change_file(FileId(file.0), text);
- }
+ } else {
+ None
+ };
+ change.change_file(file.file_id, text);
}
- }
- self.analysis_host.apply_change(change);
- Some(libs)
- }
+ change
+ };
- pub fn add_lib(&mut self, data: LibraryData) {
- let mut change = AnalysisChange::new();
- change.add_library(data);
self.analysis_host.apply_change(change);
+ true
}
- pub fn snapshot(&self) -> GlobalStateSnapshot {
+ pub(crate) fn snapshot(&self) -> GlobalStateSnapshot {
GlobalStateSnapshot {
config: self.config.clone(),
workspaces: Arc::clone(&self.workspaces),
}
}
- pub fn maybe_collect_garbage(&mut self) {
+ pub(crate) fn maybe_collect_garbage(&mut self) {
self.analysis_host.maybe_collect_garbage()
}
- pub fn collect_garbage(&mut self) {
+ pub(crate) fn collect_garbage(&mut self) {
self.analysis_host.collect_garbage()
}
- pub fn complete_request(&mut self, request: CompletedRequest) {
+ pub(crate) fn complete_request(&mut self, request: RequestMetrics) {
self.latest_requests.write().record(request)
}
}
impl GlobalStateSnapshot {
- pub fn analysis(&self) -> &Analysis {
- &self.analysis
- }
-
- pub fn uri_to_file_id(&self, uri: &Url) -> Result<FileId> {
- let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
- let file = self.vfs.read().path2file(&path).ok_or_else(|| {
- // Show warning as this file is outside current workspace
- // FIXME: just handle such files, and remove `LspError::UNKNOWN_FILE`.
- LspError {
- code: LspError::UNKNOWN_FILE,
- message: "Rust file outside current workspace is not supported yet.".to_string(),
- }
- })?;
- Ok(FileId(file.0))
+ pub(crate) fn url_to_file_id(&self, url: &Url) -> Result<FileId> {
+ let path = from_proto::abs_path(url)?;
+ let path = path.into();
+ let res =
+ self.vfs.read().0.file_id(&path).ok_or_else(|| format!("file not found: {}", path))?;
+ Ok(res)
}
- pub fn file_id_to_uri(&self, id: FileId) -> Result<Url> {
- let path = self.vfs.read().file2path(VfsFile(id.0));
- let url = url_from_path_with_drive_lowercasing(path)?;
-
- Ok(url)
+ pub(crate) fn file_id_to_url(&self, id: FileId) -> Url {
+ file_id_to_url(&self.vfs.read().0, id)
}
- pub fn file_id_to_path(&self, id: FileId) -> PathBuf {
- self.vfs.read().file2path(VfsFile(id.0))
+ pub(crate) fn file_line_endings(&self, id: FileId) -> LineEndings {
+ self.vfs.read().1[&id]
}
- pub fn file_line_endings(&self, id: FileId) -> LineEndings {
- self.vfs.read().file_line_endings(VfsFile(id.0))
+ pub(crate) fn anchored_path(&self, file_id: FileId, path: &str) -> Url {
+ let mut base = self.vfs.read().0.file_path(file_id);
+ base.pop();
+ let path = base.join(path);
+ let path = path.as_path().unwrap();
+ url_from_abs_path(&path)
}
- pub fn path_to_uri(&self, root: SourceRootId, path: &RelativePathBuf) -> Result<Url> {
- let base = self.vfs.read().root2path(VfsRoot(root.0));
- let path = path.to_path(base);
- let url = Url::from_file_path(&path)
- .map_err(|_| format!("can't convert path to url: {}", path.display()))?;
- Ok(url)
+ pub(crate) fn cargo_target_for_crate_root(
+ &self,
+ crate_id: CrateId,
+ ) -> Option<(&CargoWorkspace, Target)> {
+ let file_id = self.analysis.crate_root(crate_id).ok()?;
+ let path = self.vfs.read().0.file_path(file_id);
+ let path = path.as_path()?;
+ self.workspaces.iter().find_map(|ws| match ws {
+ ProjectWorkspace::Cargo { cargo, .. } => {
+ cargo.target_by_root(&path).map(|it| (cargo, it))
+ }
+ ProjectWorkspace::Json { .. } => None,
+ })
}
- pub fn status(&self) -> String {
+ pub(crate) fn status(&self) -> String {
let mut buf = String::new();
if self.workspaces.is_empty() {
buf.push_str("no workspaces\n")
);
buf
}
+}
+
+pub(crate) fn file_id_to_url(vfs: &vfs::Vfs, id: FileId) -> Url {
+ let path = vfs.file_path(id);
+ let path = path.as_path().unwrap();
+ url_from_abs_path(&path)
+}
+
+#[derive(Default)]
+pub(crate) struct ProjectFolders {
+ pub(crate) load: Vec<vfs::loader::Entry>,
+ pub(crate) watch: Vec<usize>,
+ pub(crate) source_root_config: SourceRootConfig,
+}
+
+impl ProjectFolders {
+ pub(crate) fn new(workspaces: &[ProjectWorkspace]) -> ProjectFolders {
+ let mut res = ProjectFolders::default();
+ let mut fsc = FileSetConfig::builder();
+ let mut local_filesets = vec![];
+
+ for root in workspaces.iter().flat_map(|it| it.to_roots()) {
+ let path = root.path().to_owned();
+
+ let mut file_set_roots: Vec<VfsPath> = vec![];
+
+ let entry = if root.is_member() {
+ vfs::loader::Entry::local_cargo_package(path.to_path_buf())
+ } else {
+ vfs::loader::Entry::cargo_package_dependency(path.to_path_buf())
+ };
+ res.load.push(entry);
+ if root.is_member() {
+ res.watch.push(res.load.len() - 1);
+ }
+
+ if let Some(out_dir) = root.out_dir() {
+ let out_dir = AbsPathBuf::try_from(out_dir.to_path_buf()).unwrap();
+ res.load.push(vfs::loader::Entry::rs_files_recursively(out_dir.clone()));
+ if root.is_member() {
+ res.watch.push(res.load.len() - 1);
+ }
+ file_set_roots.push(out_dir.into());
+ }
+ file_set_roots.push(path.to_path_buf().into());
+
+ if root.is_member() {
+ local_filesets.push(fsc.len());
+ }
+ fsc.add_file_set(file_set_roots)
+ }
+
+ let fsc = fsc.build();
+ res.source_root_config = SourceRootConfig { fsc, local_filesets };
+
+ res
+ }
+}
- pub fn workspace_root_for(&self, file_id: FileId) -> Option<&Path> {
- let path = self.vfs.read().file2path(VfsFile(file_id.0));
- self.workspaces.iter().find_map(|ws| ws.workspace_root_for(&path))
+#[derive(Default, Debug)]
+pub(crate) struct SourceRootConfig {
+ pub(crate) fsc: FileSetConfig,
+ pub(crate) local_filesets: Vec<usize>,
+}
+
+impl SourceRootConfig {
+ pub(crate) fn partition(&self, vfs: &vfs::Vfs) -> Vec<SourceRoot> {
+ self.fsc
+ .partition(vfs)
+ .into_iter()
+ .enumerate()
+ .map(|(idx, file_set)| {
+ let is_local = self.local_filesets.contains(&idx);
+ if is_local {
+ SourceRoot::new_local(file_set)
+ } else {
+ SourceRoot::new_library(file_set)
+ }
+ })
+ .collect()
}
}