From 80be61ed78e8410e013cb94879435d54a4907c30 Mon Sep 17 00:00:00 2001 From: Aleksey Kladov Date: Sun, 2 Sep 2018 14:46:15 +0300 Subject: project model --- crates/server/src/lib.rs | 1 + crates/server/src/main_loop/mod.rs | 39 ++++++++++++++++++++++----- crates/server/src/project_model.rs | 54 ++++++++++++++++++++++++++++++------- crates/server/src/req.rs | 8 ++++++ crates/server/src/server_world.rs | 10 ++++++- crates/server/src/thread_watcher.rs | 33 +++++++++++++++++++++++ crates/server/src/vfs.rs | 28 +++++-------------- 7 files changed, 134 insertions(+), 39 deletions(-) create mode 100644 crates/server/src/thread_watcher.rs (limited to 'crates/server/src') diff --git a/crates/server/src/lib.rs b/crates/server/src/lib.rs index 096b94a6d..d874ecf84 100644 --- a/crates/server/src/lib.rs +++ b/crates/server/src/lib.rs @@ -30,6 +30,7 @@ mod vfs; mod path_map; mod server_world; mod project_model; +mod thread_watcher; pub type Result = ::std::result::Result; pub use caps::server_capabilities; diff --git a/crates/server/src/main_loop/mod.rs b/crates/server/src/main_loop/mod.rs index ff267fcad..52fc00c9c 100644 --- a/crates/server/src/main_loop/mod.rs +++ b/crates/server/src/main_loop/mod.rs @@ -22,6 +22,7 @@ use { vfs::{self, FileEvent}, server_world::{ServerWorldState, ServerWorld}, main_loop::subscriptions::{Subscriptions}, + project_model::{CargoWorkspace, workspace_loader}, }; #[derive(Debug)] @@ -37,20 +38,24 @@ pub fn main_loop( ) -> Result<()> { let pool = ThreadPool::new(4); let (task_sender, task_receiver) = bounded::(16); - let (fs_events_receiver, watcher) = vfs::watch(vec![root]); + let (fs_events_receiver, watcher) = vfs::watch(vec![root.clone()]); + let (ws_root_sender, ws_receiver, ws_watcher) = workspace_loader(); + ws_root_sender.send(root); info!("server initialized, serving requests"); let mut state = ServerWorldState::new(); let mut pending_requests = HashMap::new(); let mut subs = Subscriptions::new(); - let res = main_loop_inner( + let main_res = main_loop_inner( &pool, msg_receriver, msg_sender, task_receiver.clone(), task_sender, fs_events_receiver, + ws_root_sender, + ws_receiver, &mut state, &mut pending_requests, &mut subs, @@ -63,10 +68,14 @@ pub fn main_loop( pool.join(); info!("...threadpool has finished"); - info!("waiting for file watcher to finish..."); - watcher.stop()?; - info!("...file watcher has finished"); - res + let vfs_res = watcher.stop(); + let ws_res = ws_watcher.stop(); + + main_res?; + vfs_res?; + ws_res?; + + Ok(()) } fn main_loop_inner( @@ -76,6 +85,8 @@ fn main_loop_inner( task_receiver: Receiver, task_sender: Sender, fs_receiver: Receiver>, + _ws_roots_sender: Sender, + ws_receiver: Receiver>, state: &mut ServerWorldState, pending_requests: &mut HashMap, subs: &mut Subscriptions, @@ -87,6 +98,7 @@ fn main_loop_inner( Msg(RawMessage), Task(Task), Fs(Vec), + Ws(Result), FsWatcherDead, } trace!("selecting"); @@ -100,6 +112,10 @@ fn main_loop_inner( Some(events) => Event::Fs(events), None => Event::FsWatcherDead, } + recv(ws_receiver, ws) => match ws { + None => bail!("workspace watcher died"), + Some(ws) => Event::Ws(ws), + } }; trace!("selected {:?}", event); let mut state_changed = false; @@ -111,6 +127,17 @@ fn main_loop_inner( state.apply_fs_changes(events); state_changed = true; } + Event::Ws(ws) => { + match ws { + Ok(ws) => { + let not = RawNotification::new::(vec![ws.clone()]); + msg_sender.send(RawMessage::Notification(not)); + state.set_workspaces(vec![ws]); + state_changed = true; + } + Err(e) => warn!("loading workspace failed: {}", e), + } + } Event::Msg(msg) => { match msg { RawMessage::Request(req) => { diff --git a/crates/server/src/project_model.rs b/crates/server/src/project_model.rs index a33b34dd0..1c5954dad 100644 --- a/crates/server/src/project_model.rs +++ b/crates/server/src/project_model.rs @@ -2,30 +2,35 @@ use std::{ collections::HashMap, path::{Path, PathBuf}, }; -use libsyntax2::SmolStr; use cargo_metadata::{metadata_run, CargoOpt}; -use Result; +use crossbeam_channel::{bounded, Sender, Receiver}; +use libsyntax2::SmolStr; + +use { + Result, + thread_watcher::ThreadWatcher, +}; -#[derive(Debug)] +#[derive(Debug, Serialize, Clone)] pub struct CargoWorkspace { ws_members: Vec, packages: Vec, targets: Vec, } -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Serialize)] pub struct Package(usize); -#[derive(Clone, Copy, Debug)] +#[derive(Clone, Copy, Debug, Serialize)] pub struct Target(usize); -#[derive(Debug)] +#[derive(Debug, Serialize, Clone)] struct PackageData { name: SmolStr, manifest: PathBuf, targets: Vec } -#[derive(Debug)] +#[derive(Debug, Serialize, Clone)] struct TargetData { pkg: Package, name: SmolStr, @@ -33,7 +38,7 @@ struct TargetData { kind: TargetKind, } -#[derive(Clone, Copy, PartialEq, Eq, Debug)] +#[derive(Debug, Serialize, Clone, Copy, PartialEq, Eq)] pub enum TargetKind { Bin, Lib, Example, Test, Bench, Other, } @@ -66,9 +71,10 @@ impl Target { } impl CargoWorkspace { - pub fn from_path(path: &Path) -> Result { + pub fn from_cargo_metadata(path: &Path) -> Result { + let cargo_toml = find_cargo_toml(path)?; let meta = metadata_run( - Some(path), + Some(cargo_toml.as_path()), true, Some(CargoOpt::AllFeatures) ).map_err(|e| format_err!("cargo metadata failed: {}", e))?; @@ -121,6 +127,21 @@ impl CargoWorkspace { } } +fn find_cargo_toml(path: &Path) -> Result { + if path.ends_with("Cargo.toml") { + return Ok(path.to_path_buf()); + } + let mut curr = Some(path); + while let Some(path) = curr { + let candidate = path.join("Cargo.toml"); + if candidate.exists() { + return Ok(candidate); + } + curr = path.parent(); + } + bail!("can't find Cargo.toml at {}", path.display()) +} + impl TargetKind { fn new(kinds: &[String]) -> TargetKind { for kind in kinds { @@ -136,3 +157,16 @@ impl TargetKind { TargetKind::Other } } + +pub fn workspace_loader() -> (Sender, Receiver>, ThreadWatcher) { + let (path_sender, path_receiver) = bounded::(16); + let (ws_sender, ws_receiver) = bounded::>(1); + let thread = ThreadWatcher::spawn("workspace loader", move || { + path_receiver + .into_iter() + .map(|path| CargoWorkspace::from_cargo_metadata(path.as_path())) + .for_each(|it| ws_sender.send(it)) + }); + + (path_sender, ws_receiver, thread) +} diff --git a/crates/server/src/req.rs b/crates/server/src/req.rs index 893cbde81..b9e0c3796 100644 --- a/crates/server/src/req.rs +++ b/crates/server/src/req.rs @@ -1,6 +1,7 @@ use std::collections::HashMap; use languageserver_types::{TextDocumentIdentifier, Range, Url, Position, Location}; use url_serde; +use project_model::CargoWorkspace; pub use languageserver_types::{ request::*, notification::*, @@ -167,3 +168,10 @@ pub enum FileSystemEdit { dst: Url, } } + +pub enum DidReloadWorkspace {} + +impl Notification for DidReloadWorkspace { + const METHOD: &'static str = "m/didReloadWorkspace"; + type Params = Vec; +} diff --git a/crates/server/src/server_world.rs b/crates/server/src/server_world.rs index d99ef661e..4d5c50428 100644 --- a/crates/server/src/server_world.rs +++ b/crates/server/src/server_world.rs @@ -2,6 +2,7 @@ use std::{ fs, path::{PathBuf, Path}, collections::HashMap, + sync::Arc, }; use languageserver_types::Url; @@ -11,10 +12,12 @@ use { Result, path_map::PathMap, vfs::{FileEvent, FileEventKind}, + project_model::CargoWorkspace, }; #[derive(Debug)] pub struct ServerWorldState { + pub workspaces: Arc>, pub analysis_host: AnalysisHost, pub path_map: PathMap, pub mem_map: HashMap>, @@ -22,6 +25,7 @@ pub struct ServerWorldState { #[derive(Clone)] pub struct ServerWorld { + pub workspaces: Arc>, pub analysis: Analysis, pub path_map: PathMap, } @@ -29,6 +33,7 @@ pub struct ServerWorld { impl ServerWorldState { pub fn new() -> ServerWorldState { ServerWorldState { + workspaces: Arc::new(Vec::new()), analysis_host: AnalysisHost::new(), path_map: PathMap::new(), mem_map: HashMap::new(), @@ -89,9 +94,12 @@ impl ServerWorldState { self.analysis_host.change_file(file_id, text); Ok(file_id) } - + pub fn set_workspaces(&mut self, ws: Vec) { + self.workspaces = Arc::new(ws); + } pub fn snapshot(&self) -> ServerWorld { ServerWorld { + workspaces: Arc::clone(&self.workspaces), analysis: self.analysis_host.analysis(self.path_map.clone()), path_map: self.path_map.clone() } diff --git a/crates/server/src/thread_watcher.rs b/crates/server/src/thread_watcher.rs new file mode 100644 index 000000000..98bcdfd6c --- /dev/null +++ b/crates/server/src/thread_watcher.rs @@ -0,0 +1,33 @@ +use std::thread; +use drop_bomb::DropBomb; +use Result; + +pub struct ThreadWatcher { + name: &'static str, + thread: thread::JoinHandle<()>, + bomb: DropBomb, +} + +impl ThreadWatcher { + pub fn spawn(name: &'static str, f: impl FnOnce() + Send + 'static) -> ThreadWatcher { + let thread = thread::spawn(f); + ThreadWatcher { + name, + thread, + bomb: DropBomb::new(format!("ThreadWatcher {} was not stopped", name)), + } + } + + pub fn stop(mut self) -> Result<()> { + info!("waiting for {} to finish ...", self.name); + let name = self.name; + self.bomb.defuse(); + let res = self.thread.join() + .map_err(|_| format_err!("ThreadWatcher {} died", name)); + match &res { + Ok(()) => info!("... {} terminated with ok", name), + Err(_) => error!("... {} terminated with err", name) + } + res + } +} diff --git a/crates/server/src/vfs.rs b/crates/server/src/vfs.rs index 2e4319cdb..2acc3f55f 100644 --- a/crates/server/src/vfs.rs +++ b/crates/server/src/vfs.rs @@ -1,14 +1,14 @@ use std::{ path::PathBuf, - thread, fs, }; use crossbeam_channel::{Sender, Receiver, bounded}; -use drop_bomb::DropBomb; use walkdir::WalkDir; -use Result; +use { + thread_watcher::ThreadWatcher, +}; #[derive(Debug)] @@ -24,26 +24,10 @@ pub enum FileEventKind { Remove, } -pub struct Watcher { - thread: thread::JoinHandle<()>, - bomb: DropBomb, -} - -impl Watcher { - pub fn stop(mut self) -> Result<()> { - self.bomb.defuse(); - self.thread.join() - .map_err(|_| format_err!("file watcher died")) - } -} - -pub fn watch(roots: Vec) -> (Receiver>, Watcher) { +pub fn watch(roots: Vec) -> (Receiver>, ThreadWatcher) { let (sender, receiver) = bounded(16); - let thread = thread::spawn(move || run(roots, sender)); - (receiver, Watcher { - thread, - bomb: DropBomb::new("Watcher should be stopped explicitly"), - }) + let watcher = ThreadWatcher::spawn("vfs", move || run(roots, sender)); + (receiver, watcher) } fn run(roots: Vec, sender: Sender>) { -- cgit v1.2.3