aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--crates/libeditor/src/completion.rs52
-rw-r--r--crates/server/Cargo.toml3
-rw-r--r--crates/server/src/lib.rs1
-rw-r--r--crates/server/src/main_loop/mod.rs39
-rw-r--r--crates/server/src/project_model.rs54
-rw-r--r--crates/server/src/req.rs8
-rw-r--r--crates/server/src/server_world.rs10
-rw-r--r--crates/server/src/thread_watcher.rs33
-rw-r--r--crates/server/src/vfs.rs28
-rw-r--r--crates/server/tests/heavy_tests/main.rs36
-rw-r--r--crates/server/tests/heavy_tests/support.rs65
11 files changed, 251 insertions, 78 deletions
diff --git a/crates/libeditor/src/completion.rs b/crates/libeditor/src/completion.rs
index be37fb6bf..b296f6fd5 100644
--- a/crates/libeditor/src/completion.rs
+++ b/crates/libeditor/src/completion.rs
@@ -62,16 +62,16 @@ fn is_single_segment(name_ref: ast::NameRef) -> bool {
62} 62}
63 63
64fn complete_expr_keywords(file: &File, fn_def: ast::FnDef, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) { 64fn complete_expr_keywords(file: &File, fn_def: ast::FnDef, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) {
65 acc.push(keyword("if", "if $0 { }")); 65 acc.push(keyword("if", "if $0 {}"));
66 acc.push(keyword("match", "match $0 { }")); 66 acc.push(keyword("match", "match $0 {}"));
67 acc.push(keyword("while", "while $0 { }")); 67 acc.push(keyword("while", "while $0 {}"));
68 acc.push(keyword("loop", "loop {$0}")); 68 acc.push(keyword("loop", "loop {$0}"));
69 69
70 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) { 70 if let Some(off) = name_ref.syntax().range().start().checked_sub(2.into()) {
71 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) { 71 if let Some(if_expr) = find_node_at_offset::<ast::IfExpr>(file.syntax(), off) {
72 if if_expr.syntax().range().end() < name_ref.syntax().range().start() { 72 if if_expr.syntax().range().end() < name_ref.syntax().range().start() {
73 acc.push(keyword("else", "else {$0}")); 73 acc.push(keyword("else", "else {$0}"));
74 acc.push(keyword("else if", "else if $0 { }")); 74 acc.push(keyword("else if", "else if $0 {}"));
75 } 75 }
76 } 76 }
77 } 77 }
@@ -276,9 +276,9 @@ mod tests {
276 fn quux() { 276 fn quux() {
277 <|> 277 <|>
278 } 278 }
279 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") }, 279 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") },
280 CompletionItem { name: "match", snippet: Some("match $0 { }") }, 280 CompletionItem { name: "match", snippet: Some("match $0 {}") },
281 CompletionItem { name: "while", snippet: Some("while $0 { }") }, 281 CompletionItem { name: "while", snippet: Some("while $0 {}") },
282 CompletionItem { name: "loop", snippet: Some("loop {$0}") }, 282 CompletionItem { name: "loop", snippet: Some("loop {$0}") },
283 CompletionItem { name: "return", snippet: Some("return") }]"#); 283 CompletionItem { name: "return", snippet: Some("return") }]"#);
284 } 284 }
@@ -291,12 +291,12 @@ mod tests {
291 () 291 ()
292 } <|> 292 } <|>
293 } 293 }
294 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") }, 294 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") },
295 CompletionItem { name: "match", snippet: Some("match $0 { }") }, 295 CompletionItem { name: "match", snippet: Some("match $0 {}") },
296 CompletionItem { name: "while", snippet: Some("while $0 { }") }, 296 CompletionItem { name: "while", snippet: Some("while $0 {}") },
297 CompletionItem { name: "loop", snippet: Some("loop {$0}") }, 297 CompletionItem { name: "loop", snippet: Some("loop {$0}") },
298 CompletionItem { name: "else", snippet: Some("else {$0}") }, 298 CompletionItem { name: "else", snippet: Some("else {$0}") },
299 CompletionItem { name: "else if", snippet: Some("else if $0 { }") }, 299 CompletionItem { name: "else if", snippet: Some("else if $0 {}") },
300 CompletionItem { name: "return", snippet: Some("return") }]"#); 300 CompletionItem { name: "return", snippet: Some("return") }]"#);
301 } 301 }
302 302
@@ -307,9 +307,9 @@ mod tests {
307 <|> 307 <|>
308 92 308 92
309 } 309 }
310 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") }, 310 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") },
311 CompletionItem { name: "match", snippet: Some("match $0 { }") }, 311 CompletionItem { name: "match", snippet: Some("match $0 {}") },
312 CompletionItem { name: "while", snippet: Some("while $0 { }") }, 312 CompletionItem { name: "while", snippet: Some("while $0 {}") },
313 CompletionItem { name: "loop", snippet: Some("loop {$0}") }, 313 CompletionItem { name: "loop", snippet: Some("loop {$0}") },
314 CompletionItem { name: "return", snippet: Some("return $0;") }]"#); 314 CompletionItem { name: "return", snippet: Some("return $0;") }]"#);
315 check_snippet_completion(r" 315 check_snippet_completion(r"
@@ -317,9 +317,9 @@ mod tests {
317 <|> 317 <|>
318 92 318 92
319 } 319 }
320 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") }, 320 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") },
321 CompletionItem { name: "match", snippet: Some("match $0 { }") }, 321 CompletionItem { name: "match", snippet: Some("match $0 {}") },
322 CompletionItem { name: "while", snippet: Some("while $0 { }") }, 322 CompletionItem { name: "while", snippet: Some("while $0 {}") },
323 CompletionItem { name: "loop", snippet: Some("loop {$0}") }, 323 CompletionItem { name: "loop", snippet: Some("loop {$0}") },
324 CompletionItem { name: "return", snippet: Some("return;") }]"#); 324 CompletionItem { name: "return", snippet: Some("return;") }]"#);
325 } 325 }
@@ -332,9 +332,9 @@ mod tests {
332 () => <|> 332 () => <|>
333 } 333 }
334 } 334 }
335 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") }, 335 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") },
336 CompletionItem { name: "match", snippet: Some("match $0 { }") }, 336 CompletionItem { name: "match", snippet: Some("match $0 {}") },
337 CompletionItem { name: "while", snippet: Some("while $0 { }") }, 337 CompletionItem { name: "while", snippet: Some("while $0 {}") },
338 CompletionItem { name: "loop", snippet: Some("loop {$0}") }, 338 CompletionItem { name: "loop", snippet: Some("loop {$0}") },
339 CompletionItem { name: "return", snippet: Some("return $0") }]"#); 339 CompletionItem { name: "return", snippet: Some("return $0") }]"#);
340 } 340 }
@@ -345,9 +345,9 @@ mod tests {
345 fn quux() -> i32 { 345 fn quux() -> i32 {
346 loop { <|> } 346 loop { <|> }
347 } 347 }
348 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") }, 348 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") },
349 CompletionItem { name: "match", snippet: Some("match $0 { }") }, 349 CompletionItem { name: "match", snippet: Some("match $0 {}") },
350 CompletionItem { name: "while", snippet: Some("while $0 { }") }, 350 CompletionItem { name: "while", snippet: Some("while $0 {}") },
351 CompletionItem { name: "loop", snippet: Some("loop {$0}") }, 351 CompletionItem { name: "loop", snippet: Some("loop {$0}") },
352 CompletionItem { name: "continue", snippet: Some("continue") }, 352 CompletionItem { name: "continue", snippet: Some("continue") },
353 CompletionItem { name: "break", snippet: Some("break") }, 353 CompletionItem { name: "break", snippet: Some("break") },
@@ -356,9 +356,9 @@ mod tests {
356 fn quux() -> i32 { 356 fn quux() -> i32 {
357 loop { || { <|> } } 357 loop { || { <|> } }
358 } 358 }
359 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 { }") }, 359 ", r#"[CompletionItem { name: "if", snippet: Some("if $0 {}") },
360 CompletionItem { name: "match", snippet: Some("match $0 { }") }, 360 CompletionItem { name: "match", snippet: Some("match $0 {}") },
361 CompletionItem { name: "while", snippet: Some("while $0 { }") }, 361 CompletionItem { name: "while", snippet: Some("while $0 {}") },
362 CompletionItem { name: "loop", snippet: Some("loop {$0}") }, 362 CompletionItem { name: "loop", snippet: Some("loop {$0}") },
363 CompletionItem { name: "return", snippet: Some("return $0") }]"#); 363 CompletionItem { name: "return", snippet: Some("return $0") }]"#);
364 } 364 }
diff --git a/crates/server/Cargo.toml b/crates/server/Cargo.toml
index c3e7a6238..cb96929c6 100644
--- a/crates/server/Cargo.toml
+++ b/crates/server/Cargo.toml
@@ -18,8 +18,9 @@ url_serde = "0.2.0"
18languageserver-types = "0.49.0" 18languageserver-types = "0.49.0"
19walkdir = "2.2.0" 19walkdir = "2.2.0"
20im = { version = "11.0.1", features = ["arc"] } 20im = { version = "11.0.1", features = ["arc"] }
21text_unit = { version = "0.1.2", features = ["serde"] }
22cargo_metadata = "0.6.0" 21cargo_metadata = "0.6.0"
22text_unit = { version = "0.1.2", features = ["serde"] }
23smol_str = { version = "0.1.5", features = ["serde"] }
23 24
24libsyntax2 = { path = "../libsyntax2" } 25libsyntax2 = { path = "../libsyntax2" }
25libeditor = { path = "../libeditor" } 26libeditor = { path = "../libeditor" }
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;
30mod path_map; 30mod path_map;
31mod server_world; 31mod server_world;
32mod project_model; 32mod project_model;
33mod thread_watcher;
33 34
34pub type Result<T> = ::std::result::Result<T, ::failure::Error>; 35pub type Result<T> = ::std::result::Result<T, ::failure::Error>;
35pub use caps::server_capabilities; 36pub 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 {
22 vfs::{self, FileEvent}, 22 vfs::{self, FileEvent},
23 server_world::{ServerWorldState, ServerWorld}, 23 server_world::{ServerWorldState, ServerWorld},
24 main_loop::subscriptions::{Subscriptions}, 24 main_loop::subscriptions::{Subscriptions},
25 project_model::{CargoWorkspace, workspace_loader},
25}; 26};
26 27
27#[derive(Debug)] 28#[derive(Debug)]
@@ -37,20 +38,24 @@ pub fn main_loop(
37) -> Result<()> { 38) -> Result<()> {
38 let pool = ThreadPool::new(4); 39 let pool = ThreadPool::new(4);
39 let (task_sender, task_receiver) = bounded::<Task>(16); 40 let (task_sender, task_receiver) = bounded::<Task>(16);
40 let (fs_events_receiver, watcher) = vfs::watch(vec![root]); 41 let (fs_events_receiver, watcher) = vfs::watch(vec![root.clone()]);
42 let (ws_root_sender, ws_receiver, ws_watcher) = workspace_loader();
43 ws_root_sender.send(root);
41 44
42 info!("server initialized, serving requests"); 45 info!("server initialized, serving requests");
43 let mut state = ServerWorldState::new(); 46 let mut state = ServerWorldState::new();
44 47
45 let mut pending_requests = HashMap::new(); 48 let mut pending_requests = HashMap::new();
46 let mut subs = Subscriptions::new(); 49 let mut subs = Subscriptions::new();
47 let res = main_loop_inner( 50 let main_res = main_loop_inner(
48 &pool, 51 &pool,
49 msg_receriver, 52 msg_receriver,
50 msg_sender, 53 msg_sender,
51 task_receiver.clone(), 54 task_receiver.clone(),
52 task_sender, 55 task_sender,
53 fs_events_receiver, 56 fs_events_receiver,
57 ws_root_sender,
58 ws_receiver,
54 &mut state, 59 &mut state,
55 &mut pending_requests, 60 &mut pending_requests,
56 &mut subs, 61 &mut subs,
@@ -63,10 +68,14 @@ pub fn main_loop(
63 pool.join(); 68 pool.join();
64 info!("...threadpool has finished"); 69 info!("...threadpool has finished");
65 70
66 info!("waiting for file watcher to finish..."); 71 let vfs_res = watcher.stop();
67 watcher.stop()?; 72 let ws_res = ws_watcher.stop();
68 info!("...file watcher has finished"); 73
69 res 74 main_res?;
75 vfs_res?;
76 ws_res?;
77
78 Ok(())
70} 79}
71 80
72fn main_loop_inner( 81fn main_loop_inner(
@@ -76,6 +85,8 @@ fn main_loop_inner(
76 task_receiver: Receiver<Task>, 85 task_receiver: Receiver<Task>,
77 task_sender: Sender<Task>, 86 task_sender: Sender<Task>,
78 fs_receiver: Receiver<Vec<FileEvent>>, 87 fs_receiver: Receiver<Vec<FileEvent>>,
88 _ws_roots_sender: Sender<PathBuf>,
89 ws_receiver: Receiver<Result<CargoWorkspace>>,
79 state: &mut ServerWorldState, 90 state: &mut ServerWorldState,
80 pending_requests: &mut HashMap<u64, JobHandle>, 91 pending_requests: &mut HashMap<u64, JobHandle>,
81 subs: &mut Subscriptions, 92 subs: &mut Subscriptions,
@@ -87,6 +98,7 @@ fn main_loop_inner(
87 Msg(RawMessage), 98 Msg(RawMessage),
88 Task(Task), 99 Task(Task),
89 Fs(Vec<FileEvent>), 100 Fs(Vec<FileEvent>),
101 Ws(Result<CargoWorkspace>),
90 FsWatcherDead, 102 FsWatcherDead,
91 } 103 }
92 trace!("selecting"); 104 trace!("selecting");
@@ -100,6 +112,10 @@ fn main_loop_inner(
100 Some(events) => Event::Fs(events), 112 Some(events) => Event::Fs(events),
101 None => Event::FsWatcherDead, 113 None => Event::FsWatcherDead,
102 } 114 }
115 recv(ws_receiver, ws) => match ws {
116 None => bail!("workspace watcher died"),
117 Some(ws) => Event::Ws(ws),
118 }
103 }; 119 };
104 trace!("selected {:?}", event); 120 trace!("selected {:?}", event);
105 let mut state_changed = false; 121 let mut state_changed = false;
@@ -111,6 +127,17 @@ fn main_loop_inner(
111 state.apply_fs_changes(events); 127 state.apply_fs_changes(events);
112 state_changed = true; 128 state_changed = true;
113 } 129 }
130 Event::Ws(ws) => {
131 match ws {
132 Ok(ws) => {
133 let not = RawNotification::new::<req::DidReloadWorkspace>(vec![ws.clone()]);
134 msg_sender.send(RawMessage::Notification(not));
135 state.set_workspaces(vec![ws]);
136 state_changed = true;
137 }
138 Err(e) => warn!("loading workspace failed: {}", e),
139 }
140 }
114 Event::Msg(msg) => { 141 Event::Msg(msg) => {
115 match msg { 142 match msg {
116 RawMessage::Request(req) => { 143 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::{
2 collections::HashMap, 2 collections::HashMap,
3 path::{Path, PathBuf}, 3 path::{Path, PathBuf},
4}; 4};
5use libsyntax2::SmolStr;
6use cargo_metadata::{metadata_run, CargoOpt}; 5use cargo_metadata::{metadata_run, CargoOpt};
7use Result; 6use crossbeam_channel::{bounded, Sender, Receiver};
7use libsyntax2::SmolStr;
8
9use {
10 Result,
11 thread_watcher::ThreadWatcher,
12};
8 13
9#[derive(Debug)] 14#[derive(Debug, Serialize, Clone)]
10pub struct CargoWorkspace { 15pub struct CargoWorkspace {
11 ws_members: Vec<Package>, 16 ws_members: Vec<Package>,
12 packages: Vec<PackageData>, 17 packages: Vec<PackageData>,
13 targets: Vec<TargetData>, 18 targets: Vec<TargetData>,
14} 19}
15 20
16#[derive(Clone, Copy, Debug)] 21#[derive(Clone, Copy, Debug, Serialize)]
17pub struct Package(usize); 22pub struct Package(usize);
18#[derive(Clone, Copy, Debug)] 23#[derive(Clone, Copy, Debug, Serialize)]
19pub struct Target(usize); 24pub struct Target(usize);
20 25
21#[derive(Debug)] 26#[derive(Debug, Serialize, Clone)]
22struct PackageData { 27struct PackageData {
23 name: SmolStr, 28 name: SmolStr,
24 manifest: PathBuf, 29 manifest: PathBuf,
25 targets: Vec<Target> 30 targets: Vec<Target>
26} 31}
27 32
28#[derive(Debug)] 33#[derive(Debug, Serialize, Clone)]
29struct TargetData { 34struct TargetData {
30 pkg: Package, 35 pkg: Package,
31 name: SmolStr, 36 name: SmolStr,
@@ -33,7 +38,7 @@ struct TargetData {
33 kind: TargetKind, 38 kind: TargetKind,
34} 39}
35 40
36#[derive(Clone, Copy, PartialEq, Eq, Debug)] 41#[derive(Debug, Serialize, Clone, Copy, PartialEq, Eq)]
37pub enum TargetKind { 42pub enum TargetKind {
38 Bin, Lib, Example, Test, Bench, Other, 43 Bin, Lib, Example, Test, Bench, Other,
39} 44}
@@ -66,9 +71,10 @@ impl Target {
66} 71}
67 72
68impl CargoWorkspace { 73impl CargoWorkspace {
69 pub fn from_path(path: &Path) -> Result<CargoWorkspace> { 74 pub fn from_cargo_metadata(path: &Path) -> Result<CargoWorkspace> {
75 let cargo_toml = find_cargo_toml(path)?;
70 let meta = metadata_run( 76 let meta = metadata_run(
71 Some(path), 77 Some(cargo_toml.as_path()),
72 true, 78 true,
73 Some(CargoOpt::AllFeatures) 79 Some(CargoOpt::AllFeatures)
74 ).map_err(|e| format_err!("cargo metadata failed: {}", e))?; 80 ).map_err(|e| format_err!("cargo metadata failed: {}", e))?;
@@ -121,6 +127,21 @@ impl CargoWorkspace {
121 } 127 }
122} 128}
123 129
130fn find_cargo_toml(path: &Path) -> Result<PathBuf> {
131 if path.ends_with("Cargo.toml") {
132 return Ok(path.to_path_buf());
133 }
134 let mut curr = Some(path);
135 while let Some(path) = curr {
136 let candidate = path.join("Cargo.toml");
137 if candidate.exists() {
138 return Ok(candidate);
139 }
140 curr = path.parent();
141 }
142 bail!("can't find Cargo.toml at {}", path.display())
143}
144
124impl TargetKind { 145impl TargetKind {
125 fn new(kinds: &[String]) -> TargetKind { 146 fn new(kinds: &[String]) -> TargetKind {
126 for kind in kinds { 147 for kind in kinds {
@@ -136,3 +157,16 @@ impl TargetKind {
136 TargetKind::Other 157 TargetKind::Other
137 } 158 }
138} 159}
160
161pub fn workspace_loader() -> (Sender<PathBuf>, Receiver<Result<CargoWorkspace>>, ThreadWatcher) {
162 let (path_sender, path_receiver) = bounded::<PathBuf>(16);
163 let (ws_sender, ws_receiver) = bounded::<Result<CargoWorkspace>>(1);
164 let thread = ThreadWatcher::spawn("workspace loader", move || {
165 path_receiver
166 .into_iter()
167 .map(|path| CargoWorkspace::from_cargo_metadata(path.as_path()))
168 .for_each(|it| ws_sender.send(it))
169 });
170
171 (path_sender, ws_receiver, thread)
172}
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 @@
1use std::collections::HashMap; 1use std::collections::HashMap;
2use languageserver_types::{TextDocumentIdentifier, Range, Url, Position, Location}; 2use languageserver_types::{TextDocumentIdentifier, Range, Url, Position, Location};
3use url_serde; 3use url_serde;
4use project_model::CargoWorkspace;
4 5
5pub use languageserver_types::{ 6pub use languageserver_types::{
6 request::*, notification::*, 7 request::*, notification::*,
@@ -167,3 +168,10 @@ pub enum FileSystemEdit {
167 dst: Url, 168 dst: Url,
168 } 169 }
169} 170}
171
172pub enum DidReloadWorkspace {}
173
174impl Notification for DidReloadWorkspace {
175 const METHOD: &'static str = "m/didReloadWorkspace";
176 type Params = Vec<CargoWorkspace>;
177}
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::{
2 fs, 2 fs,
3 path::{PathBuf, Path}, 3 path::{PathBuf, Path},
4 collections::HashMap, 4 collections::HashMap,
5 sync::Arc,
5}; 6};
6 7
7use languageserver_types::Url; 8use languageserver_types::Url;
@@ -11,10 +12,12 @@ use {
11 Result, 12 Result,
12 path_map::PathMap, 13 path_map::PathMap,
13 vfs::{FileEvent, FileEventKind}, 14 vfs::{FileEvent, FileEventKind},
15 project_model::CargoWorkspace,
14}; 16};
15 17
16#[derive(Debug)] 18#[derive(Debug)]
17pub struct ServerWorldState { 19pub struct ServerWorldState {
20 pub workspaces: Arc<Vec<CargoWorkspace>>,
18 pub analysis_host: AnalysisHost, 21 pub analysis_host: AnalysisHost,
19 pub path_map: PathMap, 22 pub path_map: PathMap,
20 pub mem_map: HashMap<FileId, Option<String>>, 23 pub mem_map: HashMap<FileId, Option<String>>,
@@ -22,6 +25,7 @@ pub struct ServerWorldState {
22 25
23#[derive(Clone)] 26#[derive(Clone)]
24pub struct ServerWorld { 27pub struct ServerWorld {
28 pub workspaces: Arc<Vec<CargoWorkspace>>,
25 pub analysis: Analysis, 29 pub analysis: Analysis,
26 pub path_map: PathMap, 30 pub path_map: PathMap,
27} 31}
@@ -29,6 +33,7 @@ pub struct ServerWorld {
29impl ServerWorldState { 33impl ServerWorldState {
30 pub fn new() -> ServerWorldState { 34 pub fn new() -> ServerWorldState {
31 ServerWorldState { 35 ServerWorldState {
36 workspaces: Arc::new(Vec::new()),
32 analysis_host: AnalysisHost::new(), 37 analysis_host: AnalysisHost::new(),
33 path_map: PathMap::new(), 38 path_map: PathMap::new(),
34 mem_map: HashMap::new(), 39 mem_map: HashMap::new(),
@@ -89,9 +94,12 @@ impl ServerWorldState {
89 self.analysis_host.change_file(file_id, text); 94 self.analysis_host.change_file(file_id, text);
90 Ok(file_id) 95 Ok(file_id)
91 } 96 }
92 97 pub fn set_workspaces(&mut self, ws: Vec<CargoWorkspace>) {
98 self.workspaces = Arc::new(ws);
99 }
93 pub fn snapshot(&self) -> ServerWorld { 100 pub fn snapshot(&self) -> ServerWorld {
94 ServerWorld { 101 ServerWorld {
102 workspaces: Arc::clone(&self.workspaces),
95 analysis: self.analysis_host.analysis(self.path_map.clone()), 103 analysis: self.analysis_host.analysis(self.path_map.clone()),
96 path_map: self.path_map.clone() 104 path_map: self.path_map.clone()
97 } 105 }
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 @@
1use std::thread;
2use drop_bomb::DropBomb;
3use Result;
4
5pub struct ThreadWatcher {
6 name: &'static str,
7 thread: thread::JoinHandle<()>,
8 bomb: DropBomb,
9}
10
11impl ThreadWatcher {
12 pub fn spawn(name: &'static str, f: impl FnOnce() + Send + 'static) -> ThreadWatcher {
13 let thread = thread::spawn(f);
14 ThreadWatcher {
15 name,
16 thread,
17 bomb: DropBomb::new(format!("ThreadWatcher {} was not stopped", name)),
18 }
19 }
20
21 pub fn stop(mut self) -> Result<()> {
22 info!("waiting for {} to finish ...", self.name);
23 let name = self.name;
24 self.bomb.defuse();
25 let res = self.thread.join()
26 .map_err(|_| format_err!("ThreadWatcher {} died", name));
27 match &res {
28 Ok(()) => info!("... {} terminated with ok", name),
29 Err(_) => error!("... {} terminated with err", name)
30 }
31 res
32 }
33}
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 @@
1use std::{ 1use std::{
2 path::PathBuf, 2 path::PathBuf,
3 thread,
4 fs, 3 fs,
5}; 4};
6 5
7use crossbeam_channel::{Sender, Receiver, bounded}; 6use crossbeam_channel::{Sender, Receiver, bounded};
8use drop_bomb::DropBomb;
9use walkdir::WalkDir; 7use walkdir::WalkDir;
10 8
11use Result; 9use {
10 thread_watcher::ThreadWatcher,
11};
12 12
13 13
14#[derive(Debug)] 14#[derive(Debug)]
@@ -24,26 +24,10 @@ pub enum FileEventKind {
24 Remove, 24 Remove,
25} 25}
26 26
27pub struct Watcher { 27pub fn watch(roots: Vec<PathBuf>) -> (Receiver<Vec<FileEvent>>, ThreadWatcher) {
28 thread: thread::JoinHandle<()>,
29 bomb: DropBomb,
30}
31
32impl Watcher {
33 pub fn stop(mut self) -> Result<()> {
34 self.bomb.defuse();
35 self.thread.join()
36 .map_err(|_| format_err!("file watcher died"))
37 }
38}
39
40pub fn watch(roots: Vec<PathBuf>) -> (Receiver<Vec<FileEvent>>, Watcher) {
41 let (sender, receiver) = bounded(16); 28 let (sender, receiver) = bounded(16);
42 let thread = thread::spawn(move || run(roots, sender)); 29 let watcher = ThreadWatcher::spawn("vfs", move || run(roots, sender));
43 (receiver, Watcher { 30 (receiver, watcher)
44 thread,
45 bomb: DropBomb::new("Watcher should be stopped explicitly"),
46 })
47} 31}
48 32
49fn run(roots: Vec<PathBuf>, sender: Sender<Vec<FileEvent>>) { 33fn run(roots: Vec<PathBuf>, sender: Sender<Vec<FileEvent>>) {
diff --git a/crates/server/tests/heavy_tests/main.rs b/crates/server/tests/heavy_tests/main.rs
index 94c8243b0..9c0196f22 100644
--- a/crates/server/tests/heavy_tests/main.rs
+++ b/crates/server/tests/heavy_tests/main.rs
@@ -1,5 +1,6 @@
1extern crate tempdir; 1#[macro_use]
2extern crate crossbeam_channel; 2extern crate crossbeam_channel;
3extern crate tempdir;
3extern crate languageserver_types; 4extern crate languageserver_types;
4extern crate serde; 5extern crate serde;
5extern crate serde_json; 6extern crate serde_json;
@@ -9,10 +10,12 @@ extern crate m;
9 10
10mod support; 11mod support;
11 12
12use m::req::{Runnables, RunnablesParams}; 13use m::req::{Runnables, RunnablesParams, DidReloadWorkspace};
13 14
14use support::project; 15use support::project;
15 16
17const LOG: &'static str = "WARN";
18
16#[test] 19#[test]
17fn test_runnables() { 20fn test_runnables() {
18 let server = project(r" 21 let server = project(r"
@@ -40,3 +43,32 @@ fn foo() {
40 ]"# 43 ]"#
41 ); 44 );
42} 45}
46
47#[test]
48fn test_project_model() {
49 let server = project(r#"
50//- Cargo.toml
51[package]
52name = "foo"
53version = "0.0.0"
54
55//- src/lib.rs
56pub fn foo() {}
57"#);
58 server.notification::<DidReloadWorkspace>(r#"[
59 {
60 "packages": [
61 {
62 "manifest": "$PROJECT_ROOT$/Cargo.toml",
63 "name": "foo",
64 "targets": [ 0 ]
65 }
66 ],
67 "targets": [
68 { "kind": "Lib", "name": "foo", "pkg": 0, "root": "$PROJECT_ROOT$/src/lib.rs" }
69 ],
70 "ws_members": [ 0 ]
71 }
72]"#
73 );
74}
diff --git a/crates/server/tests/heavy_tests/support.rs b/crates/server/tests/heavy_tests/support.rs
index 36ca56af3..006926216 100644
--- a/crates/server/tests/heavy_tests/support.rs
+++ b/crates/server/tests/heavy_tests/support.rs
@@ -3,16 +3,18 @@ use std::{
3 thread, 3 thread,
4 cell::{Cell, RefCell}, 4 cell::{Cell, RefCell},
5 path::PathBuf, 5 path::PathBuf,
6 time::Duration,
7 sync::Once,
6}; 8};
7 9
8use tempdir::TempDir; 10use tempdir::TempDir;
9use crossbeam_channel::{bounded, Sender, Receiver}; 11use crossbeam_channel::{bounded, after, Sender, Receiver};
10use flexi_logger::Logger; 12use flexi_logger::Logger;
11use languageserver_types::{ 13use languageserver_types::{
12 Url, 14 Url,
13 TextDocumentIdentifier, 15 TextDocumentIdentifier,
14 request::{Request, Shutdown}, 16 request::{Request, Shutdown},
15 notification::DidOpenTextDocument, 17 notification::{Notification, DidOpenTextDocument},
16 DidOpenTextDocumentParams, 18 DidOpenTextDocumentParams,
17 TextDocumentItem, 19 TextDocumentItem,
18}; 20};
@@ -23,7 +25,8 @@ use gen_lsp_server::{RawMessage, RawRequest, RawNotification};
23use m::{Result, main_loop}; 25use m::{Result, main_loop};
24 26
25pub fn project(fixture: &str) -> Server { 27pub fn project(fixture: &str) -> Server {
26 Logger::with_env_or_str("").start().unwrap(); 28 static INIT: Once = Once::new();
29 INIT.call_once(|| Logger::with_env_or_str(::LOG).start().unwrap());
27 30
28 let tmp_dir = TempDir::new("test-project") 31 let tmp_dir = TempDir::new("test-project")
29 .unwrap(); 32 .unwrap();
@@ -34,6 +37,7 @@ pub fn project(fixture: &str) -> Server {
34 () => { 37 () => {
35 if let Some(file_name) = file_name { 38 if let Some(file_name) = file_name {
36 let path = tmp_dir.path().join(file_name); 39 let path = tmp_dir.path().join(file_name);
40 fs::create_dir_all(path.parent().unwrap()).unwrap();
37 fs::write(path.as_path(), buf.as_bytes()).unwrap(); 41 fs::write(path.as_path(), buf.as_bytes()).unwrap();
38 paths.push((path, buf.clone())); 42 paths.push((path, buf.clone()));
39 } 43 }
@@ -121,6 +125,25 @@ impl Server {
121 ); 125 );
122 } 126 }
123 127
128 pub fn notification<N>(
129 &self,
130 expected: &str,
131 )
132 where
133 N: Notification,
134 {
135 let expected = expected.replace("$PROJECT_ROOT$", &self.dir.path().display().to_string());
136 let expected: Value = from_str(&expected).unwrap();
137 let actual = self.wait_for_notification(N::METHOD);
138 assert_eq!(
139 expected, actual,
140 "Expected:\n{}\n\
141 Actual:\n{}\n",
142 to_string_pretty(&expected).unwrap(),
143 to_string_pretty(&actual).unwrap(),
144 );
145 }
146
124 fn send_request<R>(&self, id: u64, params: R::Params) -> Value 147 fn send_request<R>(&self, id: u64, params: R::Params) -> Value
125 where 148 where
126 R: Request, 149 R: Request,
@@ -130,7 +153,6 @@ impl Server {
130 self.sender.as_ref() 153 self.sender.as_ref()
131 .unwrap() 154 .unwrap()
132 .send(RawMessage::Request(r)); 155 .send(RawMessage::Request(r));
133
134 while let Some(msg) = self.recv() { 156 while let Some(msg) = self.recv() {
135 match msg { 157 match msg {
136 RawMessage::Request(req) => panic!("unexpected request: {:?}", req), 158 RawMessage::Request(req) => panic!("unexpected request: {:?}", req),
@@ -146,15 +168,38 @@ impl Server {
146 } 168 }
147 panic!("no response"); 169 panic!("no response");
148 } 170 }
171 fn wait_for_notification(&self, method: &str) -> Value {
172 let f = |msg: &RawMessage| match msg {
173 RawMessage::Notification(n) if n.method == method => {
174 Some(n.params.clone())
175 }
176 _ => None,
177 };
178
179 for msg in self.messages.borrow().iter() {
180 if let Some(res) = f(msg) {
181 return res;
182 }
183 }
184 while let Some(msg) = self.recv() {
185 if let Some(res) = f(&msg) {
186 return res;
187 }
188 }
189 panic!("no response")
190 }
149 fn recv(&self) -> Option<RawMessage> { 191 fn recv(&self) -> Option<RawMessage> {
150 self.receiver.recv() 192 let timeout = Duration::from_secs(5);
151 .map(|msg| { 193 let msg = select! {
152 self.messages.borrow_mut().push(msg.clone()); 194 recv(&self.receiver, msg) => msg,
153 msg 195 recv(after(timeout)) => panic!("timed out"),
154 }) 196 };
197 msg.map(|msg| {
198 self.messages.borrow_mut().push(msg.clone());
199 msg
200 })
155 } 201 }
156 fn send_notification(&self, not: RawNotification) { 202 fn send_notification(&self, not: RawNotification) {
157
158 self.sender.as_ref() 203 self.sender.as_ref()
159 .unwrap() 204 .unwrap()
160 .send(RawMessage::Notification(not)); 205 .send(RawMessage::Notification(not));