]> git.lizzy.rs Git - rust.git/blobdiff - crates/rust-analyzer/src/global_state.rs
Merge branch 'Veetaha-feat/sync-branch'
[rust.git] / crates / rust-analyzer / src / global_state.rs
index 73b0f881d36976f668e949d444fd8b3246f406bf..7759c0ae309f9e0a38eb16ec3f0da1c4c2b3cb41 100644 (file)
@@ -3,51 +3,58 @@
 //!
 //! 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
@@ -55,85 +62,46 @@ fn create_flycheck(workspaces: &[ProjectWorkspace], config: &FlycheckConfig) ->
 /// 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 extern_dirs: FxHashSet<_> =
-            workspaces.iter().flat_map(ProjectWorkspace::out_dirs).collect();
-
-        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()
-            };
-            workspaces
-                .iter()
-                .flat_map(ProjectWorkspace::to_roots)
-                .map(|pkg_root| {
-                    let path = pkg_root.path().to_owned();
-                    if pkg_root.is_member() {
-                        local_roots.push(path.clone());
-                    }
-                    RootEntry::new(path, create_filter(pkg_root.is_member()))
-                })
-                .chain(
-                    extern_dirs
-                        .iter()
-                        .map(|path| RootEntry::new(path.to_owned(), create_filter(false))),
-                )
-                .collect()
-        };
-
-        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 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());
+        let project_folders = ProjectFolders::new(&workspaces);
 
-            // 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(),
@@ -150,104 +118,117 @@ pub fn new(
             },
         };
 
+        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),
@@ -258,61 +239,60 @@ pub fn snapshot(&self) -> GlobalStateSnapshot {
         }
     }
 
-    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(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 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 file_id_to_url(&self, id: FileId) -> Url {
+        file_id_to_url(&self.vfs.read().0, id)
     }
 
-    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_line_endings(&self, id: FileId) -> LineEndings {
+        self.vfs.read().1[&id]
     }
 
-    pub fn file_id_to_path(&self, id: FileId) -> PathBuf {
-        self.vfs.read().file2path(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 file_line_endings(&self, id: FileId) -> LineEndings {
-        self.vfs.read().file_line_endings(VfsFile(id.0))
-    }
-
-    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")
@@ -331,9 +311,85 @@ pub fn status(&self) -> String {
         );
         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
+    }
+}
+
+#[derive(Default, Debug)]
+pub(crate) struct SourceRootConfig {
+    pub(crate) fsc: FileSetConfig,
+    pub(crate) local_filesets: Vec<usize>,
+}
 
-    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))
+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()
     }
 }