From a5ef8ad05b7c1f7148c59814b55d641fd75aff75 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Wed, 19 Dec 2018 15:04:15 +0300 Subject: swtich lsp server to vfs --- Cargo.lock | 25 +++ crates/ra_analysis/src/imp.rs | 9 +- crates/ra_analysis/src/lib.rs | 8 +- crates/ra_lsp_server/Cargo.toml | 2 + crates/ra_lsp_server/src/lib.rs | 2 - crates/ra_lsp_server/src/main_loop.rs | 143 +++++++---------- crates/ra_lsp_server/src/main_loop/handlers.rs | 4 +- crates/ra_lsp_server/src/path_map.rs | 105 ------------- crates/ra_lsp_server/src/project_model.rs | 1 + crates/ra_lsp_server/src/server_world.rs | 203 ++++++++++--------------- crates/ra_lsp_server/src/vfs.rs | 67 -------- crates/ra_vfs/src/io.rs | 7 + crates/ra_vfs/src/lib.rs | 53 ++++++- crates/thread_worker/src/lib.rs | 3 + 14 files changed, 234 insertions(+), 398 deletions(-) delete mode 100644 crates/ra_lsp_server/src/path_map.rs delete mode 100644 crates/ra_lsp_server/src/vfs.rs diff --git a/Cargo.lock b/Cargo.lock index 0a7128d0c..7ebe6e67f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -541,6 +541,15 @@ dependencies = [ "parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "lock_api 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "parking_lot_core" version = "0.3.1" @@ -553,6 +562,18 @@ dependencies = [ "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "parking_lot_core" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.43 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.6.1 (registry+https://github.com/rust-lang/crates.io-index)", + "rustc_version 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", + "smallvec 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "percent-encoding" version = "1.0.1" @@ -701,10 +722,12 @@ dependencies = [ "im 12.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "languageserver-types 0.53.0 (registry+https://github.com/rust-lang/crates.io-index)", "log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)", + "parking_lot 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", "ra_analysis 0.1.0", "ra_editor 0.1.0", "ra_syntax 0.1.0", "ra_text_edit 0.1.0", + "ra_vfs 0.1.0", "rayon 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", "relative-path 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", "rustc-hash 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", @@ -1496,7 +1519,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum num_cpus 1.9.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5a69d464bdc213aaaff628444e99578ede64e9c854025aa43b9796530afa9238" "checksum owning_ref 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "49a4b8ea2179e6a2e27411d3bca09ca6dd630821cf6894c6c7c8467a8ee7ef13" "checksum parking_lot 0.6.4 (registry+https://github.com/rust-lang/crates.io-index)" = "f0802bff09003b291ba756dc7e79313e51cc31667e94afbe847def490424cde5" +"checksum parking_lot 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9723236a9525c757d9725b993511e3fc941e33f27751942232f0058298297edf" "checksum parking_lot_core 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "ad7f7e6ebdc79edff6fdcb87a55b620174f7a989e3eb31b65231f4af57f00b8c" +"checksum parking_lot_core 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "94c8c7923936b28d546dfd14d4472eaf34c99b14e1c973a32b3e6d4eb04298c9" "checksum percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "31010dd2e1ac33d5b46a5b413495239882813e0369f8ed8a5e266f173602f831" "checksum pest 2.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "a677051ad923732bb5c70f2d45f8985a96e3eee2e2bff86697e3b11b0c3fcfde" "checksum pest_derive 2.0.1 (registry+https://github.com/rust-lang/crates.io-index)" = "b76f477146419bc539a63f4ef40e902166cb43b3e51cecc71d9136fd12c567e7" diff --git a/crates/ra_analysis/src/imp.rs b/crates/ra_analysis/src/imp.rs index 51bcd5a73..54f38b285 100644 --- a/crates/ra_analysis/src/imp.rs +++ b/crates/ra_analysis/src/imp.rs @@ -43,14 +43,17 @@ impl AnalysisHostImpl { pub fn apply_change(&mut self, change: AnalysisChange) { log::info!("apply_change {:?}", change); // self.gc_syntax_trees(); + for root_id in change.new_roots { + self.db + .query_mut(ra_db::SourceRootQuery) + .set(root_id, Default::default()); + } for (root_id, root_change) in change.roots_changed { self.apply_root_change(root_id, root_change); } for (file_id, text) in change.files_changed { - self.db - .query_mut(ra_db::FileTextQuery) - .set(file_id, Arc::new(text)) + self.db.query_mut(ra_db::FileTextQuery).set(file_id, text) } if !change.libraries_added.is_empty() { let mut libraries = Vec::clone(&self.db.libraries()); diff --git a/crates/ra_analysis/src/lib.rs b/crates/ra_analysis/src/lib.rs index b806c974d..8882feca3 100644 --- a/crates/ra_analysis/src/lib.rs +++ b/crates/ra_analysis/src/lib.rs @@ -44,8 +44,9 @@ pub use ra_db::{ #[derive(Default)] pub struct AnalysisChange { + new_roots: Vec, roots_changed: FxHashMap, - files_changed: Vec<(FileId, String)>, + files_changed: Vec<(FileId, Arc)>, libraries_added: Vec, crate_graph: Option, } @@ -93,6 +94,9 @@ impl AnalysisChange { pub fn new() -> AnalysisChange { AnalysisChange::default() } + pub fn add_root(&mut self, root_id: SourceRootId) { + self.new_roots.push(root_id); + } pub fn add_file( &mut self, root_id: SourceRootId, @@ -111,7 +115,7 @@ impl AnalysisChange { .added .push(file); } - pub fn change_file(&mut self, file_id: FileId, new_text: String) { + pub fn change_file(&mut self, file_id: FileId, new_text: Arc) { self.files_changed.push((file_id, new_text)) } pub fn remove_file(&mut self, root_id: SourceRootId, file_id: FileId, path: RelativePathBuf) { diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml index 4e8e09584..fc10096e5 100644 --- a/crates/ra_lsp_server/Cargo.toml +++ b/crates/ra_lsp_server/Cargo.toml @@ -25,6 +25,7 @@ cargo_metadata = "0.6.0" text_unit = { version = "0.1.2", features = ["serde"] } smol_str = { version = "0.1.5", features = ["serde"] } rustc-hash = "1.0" +parking_lot = "0.7.0" thread_worker = { path = "../thread_worker" } ra_syntax = { path = "../ra_syntax" } @@ -32,6 +33,7 @@ ra_editor = { path = "../ra_editor" } ra_text_edit = { path = "../ra_text_edit" } ra_analysis = { path = "../ra_analysis" } gen_lsp_server = { path = "../gen_lsp_server" } +ra_vfs = { path = "../ra_vfs" } [dev-dependencies] tempdir = "0.3.7" diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs index 1d7258c35..725b1258a 100644 --- a/crates/ra_lsp_server/src/lib.rs +++ b/crates/ra_lsp_server/src/lib.rs @@ -1,11 +1,9 @@ mod caps; mod conv; mod main_loop; -mod path_map; mod project_model; pub mod req; mod server_world; -mod vfs; pub type Result = ::std::result::Result; pub use crate::{caps::server_capabilities, main_loop::main_loop, main_loop::LspError}; diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs index 9d3f83b4c..7904545d3 100644 --- a/crates/ra_lsp_server/src/main_loop.rs +++ b/crates/ra_lsp_server/src/main_loop.rs @@ -1,7 +1,10 @@ mod handlers; mod subscriptions; -use std::path::PathBuf; +use std::{ + path::PathBuf, + sync::Arc, +}; use crossbeam_channel::{unbounded, select, Receiver, Sender}; use gen_lsp_server::{ @@ -9,8 +12,8 @@ use gen_lsp_server::{ }; use languageserver_types::NumberOrString; use ra_analysis::{Canceled, FileId, LibraryData}; +use ra_vfs::{VfsTask}; use rayon; -use thread_worker::Worker; use threadpool::ThreadPool; use rustc_hash::FxHashSet; use serde::{de::DeserializeOwned, Serialize}; @@ -19,10 +22,9 @@ use failure_derive::Fail; use crate::{ main_loop::subscriptions::Subscriptions, - project_model::{workspace_loader, CargoWorkspace}, + project_model::{workspace_loader}, req, server_world::{ServerWorld, ServerWorldState}, - vfs::{self, FileEvent}, Result, }; @@ -50,32 +52,42 @@ enum Task { pub fn main_loop( internal_mode: bool, - root: PathBuf, + ws_root: PathBuf, publish_decorations: bool, msg_receiver: &Receiver, msg_sender: &Sender, ) -> Result<()> { let pool = ThreadPool::new(8); let (task_sender, task_receiver) = unbounded::(); - let (fs_worker, fs_watcher) = vfs::roots_loader(); let (ws_worker, ws_watcher) = workspace_loader(); + ws_worker.send(ws_root.clone()); + // FIXME: support dynamic workspace loading. + let workspaces = match ws_worker.recv().unwrap() { + Ok(ws) => vec![ws], + Err(e) => { + log::warn!("loading workspace failed: {}", e); + Vec::new() + } + }; + ws_worker.shutdown(); + ws_watcher + .shutdown() + .map_err(|_| format_err!("ws watcher died"))?; + let mut state = ServerWorldState::new(ws_root.clone(), workspaces); + log::info!("server initialized, serving requests"); - let mut state = ServerWorldState::default(); let mut pending_requests = FxHashSet::default(); let mut subs = Subscriptions::new(); let main_res = main_loop_inner( internal_mode, publish_decorations, - root, &pool, msg_sender, msg_receiver, task_sender, task_receiver.clone(), - fs_worker, - ws_worker, &mut state, &mut pending_requests, &mut subs, @@ -88,12 +100,11 @@ pub fn main_loop( drop(pool); log::info!("...threadpool has finished"); - let fs_res = fs_watcher.shutdown(); - let ws_res = ws_watcher.shutdown(); + let vfs = Arc::try_unwrap(state.vfs).expect("all snapshots should be dead"); + let vfs_res = vfs.into_inner().shutdown(); main_res?; - fs_res.map_err(|_| format_err!("fs watcher died"))?; - ws_res.map_err(|_| format_err!("ws watcher died"))?; + vfs_res.map_err(|_| format_err!("fs watcher died"))?; Ok(()) } @@ -101,28 +112,22 @@ pub fn main_loop( fn main_loop_inner( internal_mode: bool, publish_decorations: bool, - ws_root: PathBuf, pool: &ThreadPool, msg_sender: &Sender, msg_receiver: &Receiver, task_sender: Sender, task_receiver: Receiver, - fs_worker: Worker)>, - ws_worker: Worker>, state: &mut ServerWorldState, pending_requests: &mut FxHashSet, subs: &mut Subscriptions, ) -> Result<()> { let (libdata_sender, libdata_receiver) = unbounded(); - ws_worker.send(ws_root.clone()); - fs_worker.send(ws_root.clone()); loop { #[derive(Debug)] enum Event { Msg(RawMessage), Task(Task), - Fs(PathBuf, Vec), - Ws(Result), + Vfs(VfsTask), Lib(LibraryData), } log::trace!("selecting"); @@ -132,77 +137,19 @@ fn main_loop_inner( None => bail!("client exited without shutdown"), }, recv(task_receiver, task) => Event::Task(task.unwrap()), - recv(fs_worker.out, events) => match events { - None => bail!("roots watcher died"), - Some((pb, events)) => Event::Fs(pb, events), - } - recv(ws_worker.out, ws) => match ws { - None => bail!("workspace watcher died"), - Some(ws) => Event::Ws(ws), + recv(state.vfs.read().task_receiver(), task) => match task { + None => bail!("vfs died"), + Some(task) => Event::Vfs(task), } recv(libdata_receiver, data) => Event::Lib(data.unwrap()) }; let mut state_changed = false; match event { Event::Task(task) => on_task(task, msg_sender, pending_requests), - Event::Fs(root, events) => { - log::info!("fs change, {}, {} events", root.display(), events.len()); - if root == ws_root { - state.apply_fs_changes(events); - } else { - let (files, resolver) = state.events_to_files(events); - let sender = libdata_sender.clone(); - pool.execute(move || { - let start = ::std::time::Instant::now(); - log::info!("indexing {} ... ", root.display()); - let data = LibraryData::prepare(files, resolver); - log::info!("indexed {:?} {}", start.elapsed(), root.display()); - sender.send(data); - }); - } + Event::Vfs(task) => { + state.vfs.write().handle_task(task); state_changed = true; } - Event::Ws(ws) => match ws { - Ok(ws) => { - let workspaces = vec![ws]; - feedback(internal_mode, "workspace loaded", msg_sender); - for ws in workspaces.iter() { - // Add each library as constant input. If library is - // within the workspace, don't treat it as a library. - // - // HACK: If source roots are nested, pick the outer one. - - let mut roots = ws - .packages() - .filter(|pkg| !pkg.is_member(ws)) - .filter_map(|pkg| { - let root = pkg.root(ws).to_path_buf(); - if root.starts_with(&ws_root) { - None - } else { - Some(root) - } - }) - .collect::>(); - roots.sort_by_key(|it| it.as_os_str().len()); - let unique = roots - .iter() - .enumerate() - .filter(|&(idx, long)| { - !roots[..idx].iter().any(|short| long.starts_with(short)) - }) - .map(|(_idx, root)| root); - - for root in unique { - log::debug!("sending root, {}", root.display()); - fs_worker.send(root.to_owned()); - } - } - state.set_workspaces(workspaces); - state_changed = true; - } - Err(e) => log::warn!("loading workspace failed: {}", e), - }, Event::Lib(lib) => { feedback(internal_mode, "library loaded", msg_sender); state.add_lib(lib); @@ -234,6 +181,18 @@ fn main_loop_inner( }, }; + for lib in state.process_changes() { + let (root, files) = lib; + let sender = libdata_sender.clone(); + pool.execute(move || { + let start = ::std::time::Instant::now(); + log::info!("indexing {:?} ... ", root); + let data = LibraryData::prepare(root, files); + log::info!("indexed {:?} {:?}", start.elapsed(), root); + sender.send(data); + }); + } + if state_changed { update_file_notifications_on_threadpool( pool, @@ -336,8 +295,13 @@ fn on_notification( let path = uri .to_file_path() .map_err(|()| format_err!("invalid uri: {}", uri))?; - let file_id = state.add_mem_file(path, params.text_document.text); - subs.add_sub(file_id); + if let Some(file_id) = state + .vfs + .write() + .add_file_overlay(&path, params.text_document.text) + { + subs.add_sub(FileId(file_id.0)); + } return Ok(()); } Err(not) => not, @@ -353,7 +317,7 @@ fn on_notification( .pop() .ok_or_else(|| format_err!("empty changes"))? .text; - state.change_mem_file(path.as_path(), text)?; + state.vfs.write().change_file_overlay(path.as_path(), text); return Ok(()); } Err(not) => not, @@ -364,8 +328,9 @@ fn on_notification( let path = uri .to_file_path() .map_err(|()| format_err!("invalid uri: {}", uri))?; - let file_id = state.remove_mem_file(path.as_path())?; - subs.remove_sub(file_id); + if let Some(file_id) = state.vfs.write().remove_file_overlay(path.as_path()) { + subs.remove_sub(FileId(file_id.0)); + } let params = req::PublishDiagnosticsParams { uri, diagnostics: Vec::new(), diff --git a/crates/ra_lsp_server/src/main_loop/handlers.rs b/crates/ra_lsp_server/src/main_loop/handlers.rs index acca480c7..572ae7fb5 100644 --- a/crates/ra_lsp_server/src/main_loop/handlers.rs +++ b/crates/ra_lsp_server/src/main_loop/handlers.rs @@ -326,9 +326,9 @@ pub fn handle_runnables( None => return Ok(None), }; let file_id = world.analysis().crate_root(crate_id)?; - let path = world.path_map.get_path(file_id); + let path = world.vfs.read().file2path(ra_vfs::VfsFile(file_id.0)); let res = world.workspaces.iter().find_map(|ws| { - let tgt = ws.target_by_root(path)?; + let tgt = ws.target_by_root(&path)?; let res = CargoTargetSpec { package: tgt.package(ws).name(ws).to_string(), target: tgt.name(ws).to_string(), diff --git a/crates/ra_lsp_server/src/path_map.rs b/crates/ra_lsp_server/src/path_map.rs deleted file mode 100644 index 86cf29540..000000000 --- a/crates/ra_lsp_server/src/path_map.rs +++ /dev/null @@ -1,105 +0,0 @@ -use std::{ - fmt, - path::{Component, Path, PathBuf}, -}; - -use im; -use ra_analysis::{FileId}; -use relative_path::RelativePath; - -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum Root { - Workspace, - Lib, -} - -#[derive(Default, Clone)] -pub struct PathMap { - next_id: u32, - path2id: im::HashMap, - id2path: im::HashMap, - id2root: im::HashMap, -} - -impl fmt::Debug for PathMap { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_str("PathMap { ... }") - } -} - -impl PathMap { - pub fn get_or_insert(&mut self, path: PathBuf, root: Root) -> (bool, FileId) { - let mut inserted = false; - let file_id = self - .path2id - .get(path.as_path()) - .map(|&id| id) - .unwrap_or_else(|| { - inserted = true; - let id = self.new_file_id(); - self.insert(path, id, root); - id - }); - (inserted, file_id) - } - pub fn get_id(&self, path: &Path) -> Option { - self.path2id.get(path).cloned() - } - pub fn get_path(&self, file_id: FileId) -> &Path { - self.id2path.get(&file_id).unwrap().as_path() - } - pub fn get_root(&self, file_id: FileId) -> Root { - self.id2root[&file_id] - } - fn insert(&mut self, path: PathBuf, file_id: FileId, root: Root) { - self.path2id.insert(path.clone(), file_id); - self.id2path.insert(file_id, path.clone()); - self.id2root.insert(file_id, root); - } - - fn new_file_id(&mut self) -> FileId { - let id = FileId(self.next_id); - self.next_id += 1; - id - } -} - -fn normalize(path: &Path) -> PathBuf { - let mut components = path.components().peekable(); - let mut ret = if let Some(c @ Component::Prefix(..)) = components.peek().cloned() { - components.next(); - PathBuf::from(c.as_os_str()) - } else { - PathBuf::new() - }; - - for component in components { - match component { - Component::Prefix(..) => unreachable!(), - Component::RootDir => { - ret.push(component.as_os_str()); - } - Component::CurDir => {} - Component::ParentDir => { - ret.pop(); - } - Component::Normal(c) => { - ret.push(c); - } - } - } - ret -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn test_resolve() { - let mut m = PathMap::default(); - let (_, id1) = m.get_or_insert(PathBuf::from("/foo"), Root::Workspace); - let (_, id2) = m.get_or_insert(PathBuf::from("/foo/bar.rs"), Root::Workspace); - assert_eq!(m.resolve(id1, &RelativePath::new("bar.rs")), Some(id2),) - } -} diff --git a/crates/ra_lsp_server/src/project_model.rs b/crates/ra_lsp_server/src/project_model.rs index b881f8b6f..5852a157d 100644 --- a/crates/ra_lsp_server/src/project_model.rs +++ b/crates/ra_lsp_server/src/project_model.rs @@ -69,6 +69,7 @@ impl Package { pub fn targets<'a>(self, ws: &'a CargoWorkspace) -> impl Iterator + 'a { ws.pkg(self).targets.iter().cloned() } + #[allow(unused)] pub fn is_member(self, ws: &CargoWorkspace) -> bool { ws.pkg(self).is_member } diff --git a/crates/ra_lsp_server/src/server_world.rs b/crates/ra_lsp_server/src/server_world.rs index c0d1338a2..f2fd09e85 100644 --- a/crates/ra_lsp_server/src/server_world.rs +++ b/crates/ra_lsp_server/src/server_world.rs @@ -1,154 +1,62 @@ use std::{ - fs, - path::{Path, PathBuf}, + path::{PathBuf}, sync::Arc, }; use languageserver_types::Url; use ra_analysis::{ Analysis, AnalysisChange, AnalysisHost, CrateGraph, FileId, LibraryData, + SourceRootId }; +use ra_vfs::{Vfs, VfsChange, VfsFile}; use rustc_hash::FxHashMap; -use failure::{bail, format_err}; +use relative_path::RelativePathBuf; +use parking_lot::RwLock; +use failure::{format_err}; use crate::{ - path_map::{PathMap, Root}, project_model::{CargoWorkspace, TargetKind}, - vfs::{FileEvent, FileEventKind}, Result, }; -#[derive(Debug, Default)] +#[derive(Debug)] pub struct ServerWorldState { pub workspaces: Arc>, pub analysis_host: AnalysisHost, - pub path_map: PathMap, - pub mem_map: FxHashMap>, + pub vfs: Arc>, } pub struct ServerWorld { pub workspaces: Arc>, pub analysis: Analysis, - pub path_map: PathMap, + pub vfs: Arc>, } impl ServerWorldState { - pub fn apply_fs_changes(&mut self, events: Vec) { + pub fn new(root: PathBuf, workspaces: Vec) -> 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)); - } - if inserted { - change.set_file_resolver(Arc::new(self.path_map.clone())) - } - self.analysis_host.apply_change(change); - } - pub fn events_to_files( - &mut self, - events: Vec, - ) -> (Vec<(FileId, String)>, Arc) { - 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 roots = Vec::new(); + roots.push(root); + for ws in workspaces.iter() { + for pkg in ws.packages() { + roots.push(pkg.root(&ws).to_path_buf()); } - self.analysis_host.apply_change(change); } - self.mem_map.insert(file_id, None); - file_id - } - - 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 vfs, roots) = Vfs::new(roots); + for r in roots { + change.add_root(SourceRootId(r.0)); } - Ok(()) - } - pub fn remove_mem_file(&mut self, path: &Path) -> Result { - 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); - } - self.analysis_host.apply_change(change); - } - Ok(file_id) - } - pub fn set_workspaces(&mut self, ws: Vec) { let mut crate_graph = CrateGraph::default(); let mut pkg_to_lib_crate = FxHashMap::default(); let mut pkg_crates = FxHashMap::default(); - for ws in ws.iter() { + for ws in workspaces.iter() { for pkg in ws.packages() { for tgt in pkg.targets(ws) { let root = tgt.root(ws); - if let Some(file_id) = self.path_map.get_id(root) { + if let Some(file_id) = vfs.load(root) { + let file_id = FileId(file_id.0); let crate_id = crate_graph.add_crate_root(file_id); if tgt.kind(ws) == TargetKind::Lib { pkg_to_lib_crate.insert(pkg, crate_id); @@ -170,16 +78,64 @@ impl ServerWorldState { } } } - self.workspaces = Arc::new(ws); - let mut change = AnalysisChange::new(); change.set_crate_graph(crate_graph); + + let mut analysis_host = AnalysisHost::default(); + analysis_host.apply_change(change); + ServerWorldState { + workspaces: Arc::new(workspaces), + analysis_host, + vfs: Arc::new(RwLock::new(vfs)), + } + } + + /// Returns a vec of libraries + /// FIXME: better API here + pub fn process_changes( + &mut self, + ) -> Vec<(SourceRootId, Vec<(FileId, RelativePathBuf, Arc)>)> { + let mut libs = Vec::new(); + let mut change = AnalysisChange::new(); + for c in self.vfs.write().commit_changes() { + match c { + VfsChange::AddRoot { root, files } => { + let files = files + .into_iter() + .map(|(vfsfile, path, text)| (FileId(vfsfile.0), path, text)) + .collect(); + libs.push((SourceRootId(root.0), files)); + } + 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); + } + } + } self.analysis_host.apply_change(change); + libs } + + 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 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), } } } @@ -193,15 +149,18 @@ 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)) } pub fn file_id_to_uri(&self, id: FileId) -> Result { - 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)); + let url = Url::from_file_path(&path) + .map_err(|_| format_err!("can't convert path to url: {}", path.display()))?; Ok(url) } } diff --git a/crates/ra_lsp_server/src/vfs.rs b/crates/ra_lsp_server/src/vfs.rs deleted file mode 100644 index fcf7693d8..000000000 --- a/crates/ra_lsp_server/src/vfs.rs +++ /dev/null @@ -1,67 +0,0 @@ -use std::{ - fs, - path::{Path, PathBuf}, -}; - -use walkdir::WalkDir; -use thread_worker::{WorkerHandle, Worker}; - -#[derive(Debug)] -pub struct FileEvent { - pub path: PathBuf, - pub kind: FileEventKind, -} - -#[derive(Debug)] -pub enum FileEventKind { - Add(String), -} - -pub fn roots_loader() -> (Worker)>, WorkerHandle) { - thread_worker::spawn::), _>( - "roots loader", - 128, - |input_receiver, output_sender| { - input_receiver - .map(|path| { - log::debug!("loading {} ...", path.as_path().display()); - let events = load_root(path.as_path()); - log::debug!("... loaded {}", path.as_path().display()); - (path, events) - }) - .for_each(|it| output_sender.send(it)) - }, - ) -} - -fn load_root(path: &Path) -> Vec { - let mut res = Vec::new(); - for entry in WalkDir::new(path) { - let entry = match entry { - Ok(entry) => entry, - Err(e) => { - log::warn!("watcher error: {}", e); - continue; - } - }; - if !entry.file_type().is_file() { - continue; - } - let path = entry.path(); - if path.extension().and_then(|os| os.to_str()) != Some("rs") { - continue; - } - let text = match fs::read_to_string(path) { - Ok(text) => text, - Err(e) => { - log::warn!("watcher error: {}", e); - continue; - } - }; - res.push(FileEvent { - path: path.to_owned(), - kind: FileEventKind::Add(text), - }) - } - res -} diff --git a/crates/ra_vfs/src/io.rs b/crates/ra_vfs/src/io.rs index 178c9beff..be400bae9 100644 --- a/crates/ra_vfs/src/io.rs +++ b/crates/ra_vfs/src/io.rs @@ -1,4 +1,5 @@ use std::{ + fmt, fs, path::{Path, PathBuf}, }; @@ -20,6 +21,12 @@ pub struct TaskResult { pub(crate) files: Vec<(RelativePathBuf, String)>, } +impl fmt::Debug for TaskResult { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("TaskResult { ... }") + } +} + pub(crate) type Worker = thread_worker::Worker; pub(crate) fn start() -> (Worker, WorkerHandle) { diff --git a/crates/ra_vfs/src/lib.rs b/crates/ra_vfs/src/lib.rs index 6c1af5ef9..dc980c3d2 100644 --- a/crates/ra_vfs/src/lib.rs +++ b/crates/ra_vfs/src/lib.rs @@ -15,6 +15,7 @@ mod arena; mod io; use std::{ + fmt, mem, thread, cmp::Reverse, @@ -34,6 +35,8 @@ use crate::{ arena::{ArenaId, Arena}, }; +pub use crate::io::TaskResult as VfsTask; + /// `RootFilter` is a predicate that checks if a file can belong to a root. If /// several filters match a file (nested dirs), the most nested one wins. struct RootFilter { @@ -68,7 +71,7 @@ fn has_rs_extension(p: &Path) -> bool { } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] -pub struct VfsRoot(u32); +pub struct VfsRoot(pub u32); impl ArenaId for VfsRoot { fn from_u32(idx: u32) -> VfsRoot { @@ -80,7 +83,7 @@ impl ArenaId for VfsRoot { } #[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)] -pub struct VfsFile(u32); +pub struct VfsFile(pub u32); impl ArenaId for VfsFile { fn from_u32(idx: u32) -> VfsFile { @@ -106,8 +109,14 @@ pub struct Vfs { worker_handle: WorkerHandle, } +impl fmt::Debug for Vfs { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + f.write_str("Vfs { ... }") + } +} + impl Vfs { - pub fn new(mut roots: Vec) -> Vfs { + pub fn new(mut roots: Vec) -> (Vfs, Vec) { let (worker, worker_handle) = io::start(); let mut res = Vfs { @@ -142,7 +151,32 @@ impl Vfs { }; res.worker.inp.send(task); } - res + let roots = res.roots.iter().map(|(id, _)| id).collect(); + (res, roots) + } + + pub fn path2file(&self, path: &Path) -> Option { + if let Some((_root, _path, Some(file))) = self.find_root(path) { + return Some(file); + } + None + } + + pub fn file2path(&self, file: VfsFile) -> PathBuf { + let rel_path = &self.files[file].path; + let root_path = &self.roots[self.files[file].root].root; + rel_path.to_path(root_path) + } + + pub fn file_for_path(&self, path: &Path) -> Option { + if let Some((_root, _path, Some(file))) = self.find_root(path) { + return Some(file); + } + None + } + + pub fn load(&mut self, path: &Path) -> Option { + None } pub fn task_receiver(&self) -> &Receiver { @@ -163,14 +197,17 @@ impl Vfs { self.pending_changes.push(change); } - pub fn add_file_overlay(&mut self, path: &Path, text: String) { + pub fn add_file_overlay(&mut self, path: &Path, text: String) -> Option { + let mut res = None; if let Some((root, path, file)) = self.find_root(path) { let text = Arc::new(text); let change = if let Some(file) = file { + res = Some(file); self.change_file(file, Arc::clone(&text)); VfsChange::ChangeFile { file, text } } else { let file = self.add_file(root, path.clone(), Arc::clone(&text)); + res = Some(file); VfsChange::AddFile { file, text, @@ -180,6 +217,7 @@ impl Vfs { }; self.pending_changes.push(change); } + res } pub fn change_file_overlay(&mut self, path: &Path, new_text: String) { @@ -192,9 +230,11 @@ impl Vfs { } } - pub fn remove_file_overlay(&mut self, path: &Path) { + pub fn remove_file_overlay(&mut self, path: &Path) -> Option { + let mut res = None; if let Some((root, path, file)) = self.find_root(path) { let file = file.expect("can't remove a file which wasn't added"); + res = Some(file); let full_path = path.to_path(&self.roots[root].root); let change = if let Ok(text) = fs::read_to_string(&full_path) { let text = Arc::new(text); @@ -206,6 +246,7 @@ impl Vfs { }; self.pending_changes.push(change); } + res } pub fn commit_changes(&mut self) -> Vec { diff --git a/crates/thread_worker/src/lib.rs b/crates/thread_worker/src/lib.rs index 24d7fcce1..12e8bf17e 100644 --- a/crates/thread_worker/src/lib.rs +++ b/crates/thread_worker/src/lib.rs @@ -37,6 +37,9 @@ impl Worker { pub fn send(&self, item: I) { self.inp.send(item) } + pub fn recv(&self) -> Option { + self.out.recv() + } } impl WorkerHandle { -- cgit v1.2.3