mirror of
https://github.com/rust-lang/rust-analyzer.git
synced 2025-10-02 22:54:58 +00:00
project model
This commit is contained in:
parent
7fad13de73
commit
80be61ed78
11 changed files with 251 additions and 78 deletions
|
@ -18,8 +18,9 @@ url_serde = "0.2.0"
|
||||||
languageserver-types = "0.49.0"
|
languageserver-types = "0.49.0"
|
||||||
walkdir = "2.2.0"
|
walkdir = "2.2.0"
|
||||||
im = { version = "11.0.1", features = ["arc"] }
|
im = { version = "11.0.1", features = ["arc"] }
|
||||||
text_unit = { version = "0.1.2", features = ["serde"] }
|
|
||||||
cargo_metadata = "0.6.0"
|
cargo_metadata = "0.6.0"
|
||||||
|
text_unit = { version = "0.1.2", features = ["serde"] }
|
||||||
|
smol_str = { version = "0.1.5", features = ["serde"] }
|
||||||
|
|
||||||
libsyntax2 = { path = "../libsyntax2" }
|
libsyntax2 = { path = "../libsyntax2" }
|
||||||
libeditor = { path = "../libeditor" }
|
libeditor = { path = "../libeditor" }
|
||||||
|
|
|
@ -30,6 +30,7 @@ mod vfs;
|
||||||
mod path_map;
|
mod path_map;
|
||||||
mod server_world;
|
mod server_world;
|
||||||
mod project_model;
|
mod project_model;
|
||||||
|
mod thread_watcher;
|
||||||
|
|
||||||
pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
|
pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
|
||||||
pub use caps::server_capabilities;
|
pub use caps::server_capabilities;
|
||||||
|
|
|
@ -22,6 +22,7 @@ use {
|
||||||
vfs::{self, FileEvent},
|
vfs::{self, FileEvent},
|
||||||
server_world::{ServerWorldState, ServerWorld},
|
server_world::{ServerWorldState, ServerWorld},
|
||||||
main_loop::subscriptions::{Subscriptions},
|
main_loop::subscriptions::{Subscriptions},
|
||||||
|
project_model::{CargoWorkspace, workspace_loader},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -37,20 +38,24 @@ pub fn main_loop(
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let pool = ThreadPool::new(4);
|
let pool = ThreadPool::new(4);
|
||||||
let (task_sender, task_receiver) = bounded::<Task>(16);
|
let (task_sender, task_receiver) = bounded::<Task>(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");
|
info!("server initialized, serving requests");
|
||||||
let mut state = ServerWorldState::new();
|
let mut state = ServerWorldState::new();
|
||||||
|
|
||||||
let mut pending_requests = HashMap::new();
|
let mut pending_requests = HashMap::new();
|
||||||
let mut subs = Subscriptions::new();
|
let mut subs = Subscriptions::new();
|
||||||
let res = main_loop_inner(
|
let main_res = main_loop_inner(
|
||||||
&pool,
|
&pool,
|
||||||
msg_receriver,
|
msg_receriver,
|
||||||
msg_sender,
|
msg_sender,
|
||||||
task_receiver.clone(),
|
task_receiver.clone(),
|
||||||
task_sender,
|
task_sender,
|
||||||
fs_events_receiver,
|
fs_events_receiver,
|
||||||
|
ws_root_sender,
|
||||||
|
ws_receiver,
|
||||||
&mut state,
|
&mut state,
|
||||||
&mut pending_requests,
|
&mut pending_requests,
|
||||||
&mut subs,
|
&mut subs,
|
||||||
|
@ -63,10 +68,14 @@ pub fn main_loop(
|
||||||
pool.join();
|
pool.join();
|
||||||
info!("...threadpool has finished");
|
info!("...threadpool has finished");
|
||||||
|
|
||||||
info!("waiting for file watcher to finish...");
|
let vfs_res = watcher.stop();
|
||||||
watcher.stop()?;
|
let ws_res = ws_watcher.stop();
|
||||||
info!("...file watcher has finished");
|
|
||||||
res
|
main_res?;
|
||||||
|
vfs_res?;
|
||||||
|
ws_res?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_loop_inner(
|
fn main_loop_inner(
|
||||||
|
@ -76,6 +85,8 @@ fn main_loop_inner(
|
||||||
task_receiver: Receiver<Task>,
|
task_receiver: Receiver<Task>,
|
||||||
task_sender: Sender<Task>,
|
task_sender: Sender<Task>,
|
||||||
fs_receiver: Receiver<Vec<FileEvent>>,
|
fs_receiver: Receiver<Vec<FileEvent>>,
|
||||||
|
_ws_roots_sender: Sender<PathBuf>,
|
||||||
|
ws_receiver: Receiver<Result<CargoWorkspace>>,
|
||||||
state: &mut ServerWorldState,
|
state: &mut ServerWorldState,
|
||||||
pending_requests: &mut HashMap<u64, JobHandle>,
|
pending_requests: &mut HashMap<u64, JobHandle>,
|
||||||
subs: &mut Subscriptions,
|
subs: &mut Subscriptions,
|
||||||
|
@ -87,6 +98,7 @@ fn main_loop_inner(
|
||||||
Msg(RawMessage),
|
Msg(RawMessage),
|
||||||
Task(Task),
|
Task(Task),
|
||||||
Fs(Vec<FileEvent>),
|
Fs(Vec<FileEvent>),
|
||||||
|
Ws(Result<CargoWorkspace>),
|
||||||
FsWatcherDead,
|
FsWatcherDead,
|
||||||
}
|
}
|
||||||
trace!("selecting");
|
trace!("selecting");
|
||||||
|
@ -100,6 +112,10 @@ fn main_loop_inner(
|
||||||
Some(events) => Event::Fs(events),
|
Some(events) => Event::Fs(events),
|
||||||
None => Event::FsWatcherDead,
|
None => Event::FsWatcherDead,
|
||||||
}
|
}
|
||||||
|
recv(ws_receiver, ws) => match ws {
|
||||||
|
None => bail!("workspace watcher died"),
|
||||||
|
Some(ws) => Event::Ws(ws),
|
||||||
|
}
|
||||||
};
|
};
|
||||||
trace!("selected {:?}", event);
|
trace!("selected {:?}", event);
|
||||||
let mut state_changed = false;
|
let mut state_changed = false;
|
||||||
|
@ -111,6 +127,17 @@ fn main_loop_inner(
|
||||||
state.apply_fs_changes(events);
|
state.apply_fs_changes(events);
|
||||||
state_changed = true;
|
state_changed = true;
|
||||||
}
|
}
|
||||||
|
Event::Ws(ws) => {
|
||||||
|
match ws {
|
||||||
|
Ok(ws) => {
|
||||||
|
let not = RawNotification::new::<req::DidReloadWorkspace>(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) => {
|
Event::Msg(msg) => {
|
||||||
match msg {
|
match msg {
|
||||||
RawMessage::Request(req) => {
|
RawMessage::Request(req) => {
|
||||||
|
|
|
@ -2,30 +2,35 @@ use std::{
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
path::{Path, PathBuf},
|
path::{Path, PathBuf},
|
||||||
};
|
};
|
||||||
use libsyntax2::SmolStr;
|
|
||||||
use cargo_metadata::{metadata_run, CargoOpt};
|
use cargo_metadata::{metadata_run, CargoOpt};
|
||||||
use Result;
|
use crossbeam_channel::{bounded, Sender, Receiver};
|
||||||
|
use libsyntax2::SmolStr;
|
||||||
|
|
||||||
#[derive(Debug)]
|
use {
|
||||||
|
Result,
|
||||||
|
thread_watcher::ThreadWatcher,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Debug, Serialize, Clone)]
|
||||||
pub struct CargoWorkspace {
|
pub struct CargoWorkspace {
|
||||||
ws_members: Vec<Package>,
|
ws_members: Vec<Package>,
|
||||||
packages: Vec<PackageData>,
|
packages: Vec<PackageData>,
|
||||||
targets: Vec<TargetData>,
|
targets: Vec<TargetData>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Serialize)]
|
||||||
pub struct Package(usize);
|
pub struct Package(usize);
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug, Serialize)]
|
||||||
pub struct Target(usize);
|
pub struct Target(usize);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize, Clone)]
|
||||||
struct PackageData {
|
struct PackageData {
|
||||||
name: SmolStr,
|
name: SmolStr,
|
||||||
manifest: PathBuf,
|
manifest: PathBuf,
|
||||||
targets: Vec<Target>
|
targets: Vec<Target>
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Serialize, Clone)]
|
||||||
struct TargetData {
|
struct TargetData {
|
||||||
pkg: Package,
|
pkg: Package,
|
||||||
name: SmolStr,
|
name: SmolStr,
|
||||||
|
@ -33,7 +38,7 @@ struct TargetData {
|
||||||
kind: TargetKind,
|
kind: TargetKind,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug)]
|
#[derive(Debug, Serialize, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum TargetKind {
|
pub enum TargetKind {
|
||||||
Bin, Lib, Example, Test, Bench, Other,
|
Bin, Lib, Example, Test, Bench, Other,
|
||||||
}
|
}
|
||||||
|
@ -66,9 +71,10 @@ impl Target {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CargoWorkspace {
|
impl CargoWorkspace {
|
||||||
pub fn from_path(path: &Path) -> Result<CargoWorkspace> {
|
pub fn from_cargo_metadata(path: &Path) -> Result<CargoWorkspace> {
|
||||||
|
let cargo_toml = find_cargo_toml(path)?;
|
||||||
let meta = metadata_run(
|
let meta = metadata_run(
|
||||||
Some(path),
|
Some(cargo_toml.as_path()),
|
||||||
true,
|
true,
|
||||||
Some(CargoOpt::AllFeatures)
|
Some(CargoOpt::AllFeatures)
|
||||||
).map_err(|e| format_err!("cargo metadata failed: {}", e))?;
|
).map_err(|e| format_err!("cargo metadata failed: {}", e))?;
|
||||||
|
@ -121,6 +127,21 @@ impl CargoWorkspace {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn find_cargo_toml(path: &Path) -> Result<PathBuf> {
|
||||||
|
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 {
|
impl TargetKind {
|
||||||
fn new(kinds: &[String]) -> TargetKind {
|
fn new(kinds: &[String]) -> TargetKind {
|
||||||
for kind in kinds {
|
for kind in kinds {
|
||||||
|
@ -136,3 +157,16 @@ impl TargetKind {
|
||||||
TargetKind::Other
|
TargetKind::Other
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn workspace_loader() -> (Sender<PathBuf>, Receiver<Result<CargoWorkspace>>, ThreadWatcher) {
|
||||||
|
let (path_sender, path_receiver) = bounded::<PathBuf>(16);
|
||||||
|
let (ws_sender, ws_receiver) = bounded::<Result<CargoWorkspace>>(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)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use languageserver_types::{TextDocumentIdentifier, Range, Url, Position, Location};
|
use languageserver_types::{TextDocumentIdentifier, Range, Url, Position, Location};
|
||||||
use url_serde;
|
use url_serde;
|
||||||
|
use project_model::CargoWorkspace;
|
||||||
|
|
||||||
pub use languageserver_types::{
|
pub use languageserver_types::{
|
||||||
request::*, notification::*,
|
request::*, notification::*,
|
||||||
|
@ -167,3 +168,10 @@ pub enum FileSystemEdit {
|
||||||
dst: Url,
|
dst: Url,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum DidReloadWorkspace {}
|
||||||
|
|
||||||
|
impl Notification for DidReloadWorkspace {
|
||||||
|
const METHOD: &'static str = "m/didReloadWorkspace";
|
||||||
|
type Params = Vec<CargoWorkspace>;
|
||||||
|
}
|
||||||
|
|
|
@ -2,6 +2,7 @@ use std::{
|
||||||
fs,
|
fs,
|
||||||
path::{PathBuf, Path},
|
path::{PathBuf, Path},
|
||||||
collections::HashMap,
|
collections::HashMap,
|
||||||
|
sync::Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use languageserver_types::Url;
|
use languageserver_types::Url;
|
||||||
|
@ -11,10 +12,12 @@ use {
|
||||||
Result,
|
Result,
|
||||||
path_map::PathMap,
|
path_map::PathMap,
|
||||||
vfs::{FileEvent, FileEventKind},
|
vfs::{FileEvent, FileEventKind},
|
||||||
|
project_model::CargoWorkspace,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct ServerWorldState {
|
pub struct ServerWorldState {
|
||||||
|
pub workspaces: Arc<Vec<CargoWorkspace>>,
|
||||||
pub analysis_host: AnalysisHost,
|
pub analysis_host: AnalysisHost,
|
||||||
pub path_map: PathMap,
|
pub path_map: PathMap,
|
||||||
pub mem_map: HashMap<FileId, Option<String>>,
|
pub mem_map: HashMap<FileId, Option<String>>,
|
||||||
|
@ -22,6 +25,7 @@ pub struct ServerWorldState {
|
||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct ServerWorld {
|
pub struct ServerWorld {
|
||||||
|
pub workspaces: Arc<Vec<CargoWorkspace>>,
|
||||||
pub analysis: Analysis,
|
pub analysis: Analysis,
|
||||||
pub path_map: PathMap,
|
pub path_map: PathMap,
|
||||||
}
|
}
|
||||||
|
@ -29,6 +33,7 @@ pub struct ServerWorld {
|
||||||
impl ServerWorldState {
|
impl ServerWorldState {
|
||||||
pub fn new() -> ServerWorldState {
|
pub fn new() -> ServerWorldState {
|
||||||
ServerWorldState {
|
ServerWorldState {
|
||||||
|
workspaces: Arc::new(Vec::new()),
|
||||||
analysis_host: AnalysisHost::new(),
|
analysis_host: AnalysisHost::new(),
|
||||||
path_map: PathMap::new(),
|
path_map: PathMap::new(),
|
||||||
mem_map: HashMap::new(),
|
mem_map: HashMap::new(),
|
||||||
|
@ -89,9 +94,12 @@ impl ServerWorldState {
|
||||||
self.analysis_host.change_file(file_id, text);
|
self.analysis_host.change_file(file_id, text);
|
||||||
Ok(file_id)
|
Ok(file_id)
|
||||||
}
|
}
|
||||||
|
pub fn set_workspaces(&mut self, ws: Vec<CargoWorkspace>) {
|
||||||
|
self.workspaces = Arc::new(ws);
|
||||||
|
}
|
||||||
pub fn snapshot(&self) -> ServerWorld {
|
pub fn snapshot(&self) -> ServerWorld {
|
||||||
ServerWorld {
|
ServerWorld {
|
||||||
|
workspaces: Arc::clone(&self.workspaces),
|
||||||
analysis: self.analysis_host.analysis(self.path_map.clone()),
|
analysis: self.analysis_host.analysis(self.path_map.clone()),
|
||||||
path_map: self.path_map.clone()
|
path_map: self.path_map.clone()
|
||||||
}
|
}
|
||||||
|
|
33
crates/server/src/thread_watcher.rs
Normal file
33
crates/server/src/thread_watcher.rs
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,14 +1,14 @@
|
||||||
use std::{
|
use std::{
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
thread,
|
|
||||||
fs,
|
fs,
|
||||||
};
|
};
|
||||||
|
|
||||||
use crossbeam_channel::{Sender, Receiver, bounded};
|
use crossbeam_channel::{Sender, Receiver, bounded};
|
||||||
use drop_bomb::DropBomb;
|
|
||||||
use walkdir::WalkDir;
|
use walkdir::WalkDir;
|
||||||
|
|
||||||
use Result;
|
use {
|
||||||
|
thread_watcher::ThreadWatcher,
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -24,26 +24,10 @@ pub enum FileEventKind {
|
||||||
Remove,
|
Remove,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Watcher {
|
pub fn watch(roots: Vec<PathBuf>) -> (Receiver<Vec<FileEvent>>, ThreadWatcher) {
|
||||||
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<PathBuf>) -> (Receiver<Vec<FileEvent>>, Watcher) {
|
|
||||||
let (sender, receiver) = bounded(16);
|
let (sender, receiver) = bounded(16);
|
||||||
let thread = thread::spawn(move || run(roots, sender));
|
let watcher = ThreadWatcher::spawn("vfs", move || run(roots, sender));
|
||||||
(receiver, Watcher {
|
(receiver, watcher)
|
||||||
thread,
|
|
||||||
bomb: DropBomb::new("Watcher should be stopped explicitly"),
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(roots: Vec<PathBuf>, sender: Sender<Vec<FileEvent>>) {
|
fn run(roots: Vec<PathBuf>, sender: Sender<Vec<FileEvent>>) {
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
extern crate tempdir;
|
#[macro_use]
|
||||||
extern crate crossbeam_channel;
|
extern crate crossbeam_channel;
|
||||||
|
extern crate tempdir;
|
||||||
extern crate languageserver_types;
|
extern crate languageserver_types;
|
||||||
extern crate serde;
|
extern crate serde;
|
||||||
extern crate serde_json;
|
extern crate serde_json;
|
||||||
|
@ -9,10 +10,12 @@ extern crate m;
|
||||||
|
|
||||||
mod support;
|
mod support;
|
||||||
|
|
||||||
use m::req::{Runnables, RunnablesParams};
|
use m::req::{Runnables, RunnablesParams, DidReloadWorkspace};
|
||||||
|
|
||||||
use support::project;
|
use support::project;
|
||||||
|
|
||||||
|
const LOG: &'static str = "WARN";
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_runnables() {
|
fn test_runnables() {
|
||||||
let server = project(r"
|
let server = project(r"
|
||||||
|
@ -40,3 +43,32 @@ fn foo() {
|
||||||
]"#
|
]"#
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_project_model() {
|
||||||
|
let server = project(r#"
|
||||||
|
//- Cargo.toml
|
||||||
|
[package]
|
||||||
|
name = "foo"
|
||||||
|
version = "0.0.0"
|
||||||
|
|
||||||
|
//- src/lib.rs
|
||||||
|
pub fn foo() {}
|
||||||
|
"#);
|
||||||
|
server.notification::<DidReloadWorkspace>(r#"[
|
||||||
|
{
|
||||||
|
"packages": [
|
||||||
|
{
|
||||||
|
"manifest": "$PROJECT_ROOT$/Cargo.toml",
|
||||||
|
"name": "foo",
|
||||||
|
"targets": [ 0 ]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"targets": [
|
||||||
|
{ "kind": "Lib", "name": "foo", "pkg": 0, "root": "$PROJECT_ROOT$/src/lib.rs" }
|
||||||
|
],
|
||||||
|
"ws_members": [ 0 ]
|
||||||
|
}
|
||||||
|
]"#
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
|
@ -3,16 +3,18 @@ use std::{
|
||||||
thread,
|
thread,
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
path::PathBuf,
|
path::PathBuf,
|
||||||
|
time::Duration,
|
||||||
|
sync::Once,
|
||||||
};
|
};
|
||||||
|
|
||||||
use tempdir::TempDir;
|
use tempdir::TempDir;
|
||||||
use crossbeam_channel::{bounded, Sender, Receiver};
|
use crossbeam_channel::{bounded, after, Sender, Receiver};
|
||||||
use flexi_logger::Logger;
|
use flexi_logger::Logger;
|
||||||
use languageserver_types::{
|
use languageserver_types::{
|
||||||
Url,
|
Url,
|
||||||
TextDocumentIdentifier,
|
TextDocumentIdentifier,
|
||||||
request::{Request, Shutdown},
|
request::{Request, Shutdown},
|
||||||
notification::DidOpenTextDocument,
|
notification::{Notification, DidOpenTextDocument},
|
||||||
DidOpenTextDocumentParams,
|
DidOpenTextDocumentParams,
|
||||||
TextDocumentItem,
|
TextDocumentItem,
|
||||||
};
|
};
|
||||||
|
@ -23,7 +25,8 @@ use gen_lsp_server::{RawMessage, RawRequest, RawNotification};
|
||||||
use m::{Result, main_loop};
|
use m::{Result, main_loop};
|
||||||
|
|
||||||
pub fn project(fixture: &str) -> Server {
|
pub fn project(fixture: &str) -> Server {
|
||||||
Logger::with_env_or_str("").start().unwrap();
|
static INIT: Once = Once::new();
|
||||||
|
INIT.call_once(|| Logger::with_env_or_str(::LOG).start().unwrap());
|
||||||
|
|
||||||
let tmp_dir = TempDir::new("test-project")
|
let tmp_dir = TempDir::new("test-project")
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
@ -34,6 +37,7 @@ pub fn project(fixture: &str) -> Server {
|
||||||
() => {
|
() => {
|
||||||
if let Some(file_name) = file_name {
|
if let Some(file_name) = file_name {
|
||||||
let path = tmp_dir.path().join(file_name);
|
let path = tmp_dir.path().join(file_name);
|
||||||
|
fs::create_dir_all(path.parent().unwrap()).unwrap();
|
||||||
fs::write(path.as_path(), buf.as_bytes()).unwrap();
|
fs::write(path.as_path(), buf.as_bytes()).unwrap();
|
||||||
paths.push((path, buf.clone()));
|
paths.push((path, buf.clone()));
|
||||||
}
|
}
|
||||||
|
@ -121,6 +125,25 @@ impl Server {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn notification<N>(
|
||||||
|
&self,
|
||||||
|
expected: &str,
|
||||||
|
)
|
||||||
|
where
|
||||||
|
N: Notification,
|
||||||
|
{
|
||||||
|
let expected = expected.replace("$PROJECT_ROOT$", &self.dir.path().display().to_string());
|
||||||
|
let expected: Value = from_str(&expected).unwrap();
|
||||||
|
let actual = self.wait_for_notification(N::METHOD);
|
||||||
|
assert_eq!(
|
||||||
|
expected, actual,
|
||||||
|
"Expected:\n{}\n\
|
||||||
|
Actual:\n{}\n",
|
||||||
|
to_string_pretty(&expected).unwrap(),
|
||||||
|
to_string_pretty(&actual).unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
fn send_request<R>(&self, id: u64, params: R::Params) -> Value
|
fn send_request<R>(&self, id: u64, params: R::Params) -> Value
|
||||||
where
|
where
|
||||||
R: Request,
|
R: Request,
|
||||||
|
@ -130,7 +153,6 @@ impl Server {
|
||||||
self.sender.as_ref()
|
self.sender.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.send(RawMessage::Request(r));
|
.send(RawMessage::Request(r));
|
||||||
|
|
||||||
while let Some(msg) = self.recv() {
|
while let Some(msg) = self.recv() {
|
||||||
match msg {
|
match msg {
|
||||||
RawMessage::Request(req) => panic!("unexpected request: {:?}", req),
|
RawMessage::Request(req) => panic!("unexpected request: {:?}", req),
|
||||||
|
@ -146,15 +168,38 @@ impl Server {
|
||||||
}
|
}
|
||||||
panic!("no response");
|
panic!("no response");
|
||||||
}
|
}
|
||||||
|
fn wait_for_notification(&self, method: &str) -> Value {
|
||||||
|
let f = |msg: &RawMessage| match msg {
|
||||||
|
RawMessage::Notification(n) if n.method == method => {
|
||||||
|
Some(n.params.clone())
|
||||||
|
}
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
for msg in self.messages.borrow().iter() {
|
||||||
|
if let Some(res) = f(msg) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
while let Some(msg) = self.recv() {
|
||||||
|
if let Some(res) = f(&msg) {
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
panic!("no response")
|
||||||
|
}
|
||||||
fn recv(&self) -> Option<RawMessage> {
|
fn recv(&self) -> Option<RawMessage> {
|
||||||
self.receiver.recv()
|
let timeout = Duration::from_secs(5);
|
||||||
.map(|msg| {
|
let msg = select! {
|
||||||
|
recv(&self.receiver, msg) => msg,
|
||||||
|
recv(after(timeout)) => panic!("timed out"),
|
||||||
|
};
|
||||||
|
msg.map(|msg| {
|
||||||
self.messages.borrow_mut().push(msg.clone());
|
self.messages.borrow_mut().push(msg.clone());
|
||||||
msg
|
msg
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn send_notification(&self, not: RawNotification) {
|
fn send_notification(&self, not: RawNotification) {
|
||||||
|
|
||||||
self.sender.as_ref()
|
self.sender.as_ref()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.send(RawMessage::Notification(not));
|
.send(RawMessage::Notification(not));
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue