diff options
-rw-r--r-- | crates/libeditor/src/completion.rs | 52 | ||||
-rw-r--r-- | crates/server/Cargo.toml | 3 | ||||
-rw-r--r-- | crates/server/src/lib.rs | 1 | ||||
-rw-r--r-- | crates/server/src/main_loop/mod.rs | 39 | ||||
-rw-r--r-- | crates/server/src/project_model.rs | 54 | ||||
-rw-r--r-- | crates/server/src/req.rs | 8 | ||||
-rw-r--r-- | crates/server/src/server_world.rs | 10 | ||||
-rw-r--r-- | crates/server/src/thread_watcher.rs | 33 | ||||
-rw-r--r-- | crates/server/src/vfs.rs | 28 | ||||
-rw-r--r-- | crates/server/tests/heavy_tests/main.rs | 36 | ||||
-rw-r--r-- | crates/server/tests/heavy_tests/support.rs | 65 |
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 | ||
64 | fn complete_expr_keywords(file: &File, fn_def: ast::FnDef, name_ref: ast::NameRef, acc: &mut Vec<CompletionItem>) { | 64 | fn 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" | |||
18 | languageserver-types = "0.49.0" | 18 | languageserver-types = "0.49.0" |
19 | walkdir = "2.2.0" | 19 | walkdir = "2.2.0" |
20 | im = { version = "11.0.1", features = ["arc"] } | 20 | im = { version = "11.0.1", features = ["arc"] } |
21 | text_unit = { version = "0.1.2", features = ["serde"] } | ||
22 | cargo_metadata = "0.6.0" | 21 | cargo_metadata = "0.6.0" |
22 | text_unit = { version = "0.1.2", features = ["serde"] } | ||
23 | smol_str = { version = "0.1.5", features = ["serde"] } | ||
23 | 24 | ||
24 | libsyntax2 = { path = "../libsyntax2" } | 25 | libsyntax2 = { path = "../libsyntax2" } |
25 | libeditor = { path = "../libeditor" } | 26 | libeditor = { 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; | |||
30 | mod path_map; | 30 | mod path_map; |
31 | mod server_world; | 31 | mod server_world; |
32 | mod project_model; | 32 | mod project_model; |
33 | mod thread_watcher; | ||
33 | 34 | ||
34 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; | 35 | pub type Result<T> = ::std::result::Result<T, ::failure::Error>; |
35 | pub use caps::server_capabilities; | 36 | 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 { | |||
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 | ||
72 | fn main_loop_inner( | 81 | fn 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 | }; |
5 | use libsyntax2::SmolStr; | ||
6 | use cargo_metadata::{metadata_run, CargoOpt}; | 5 | use cargo_metadata::{metadata_run, CargoOpt}; |
7 | use Result; | 6 | use crossbeam_channel::{bounded, Sender, Receiver}; |
7 | use libsyntax2::SmolStr; | ||
8 | |||
9 | use { | ||
10 | Result, | ||
11 | thread_watcher::ThreadWatcher, | ||
12 | }; | ||
8 | 13 | ||
9 | #[derive(Debug)] | 14 | #[derive(Debug, Serialize, Clone)] |
10 | pub struct CargoWorkspace { | 15 | pub 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)] |
17 | pub struct Package(usize); | 22 | pub struct Package(usize); |
18 | #[derive(Clone, Copy, Debug)] | 23 | #[derive(Clone, Copy, Debug, Serialize)] |
19 | pub struct Target(usize); | 24 | pub struct Target(usize); |
20 | 25 | ||
21 | #[derive(Debug)] | 26 | #[derive(Debug, Serialize, Clone)] |
22 | struct PackageData { | 27 | struct 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)] |
29 | struct TargetData { | 34 | struct 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)] |
37 | pub enum TargetKind { | 42 | pub 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 | ||
68 | impl CargoWorkspace { | 73 | impl 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 | ||
130 | fn 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 | |||
124 | impl TargetKind { | 145 | impl 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 | |||
161 | pub 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 @@ | |||
1 | use std::collections::HashMap; | 1 | use std::collections::HashMap; |
2 | use languageserver_types::{TextDocumentIdentifier, Range, Url, Position, Location}; | 2 | use languageserver_types::{TextDocumentIdentifier, Range, Url, Position, Location}; |
3 | use url_serde; | 3 | use url_serde; |
4 | use project_model::CargoWorkspace; | ||
4 | 5 | ||
5 | pub use languageserver_types::{ | 6 | pub 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 | |||
172 | pub enum DidReloadWorkspace {} | ||
173 | |||
174 | impl 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 | ||
7 | use languageserver_types::Url; | 8 | use 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)] |
17 | pub struct ServerWorldState { | 19 | pub 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)] |
24 | pub struct ServerWorld { | 27 | pub 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 { | |||
29 | impl ServerWorldState { | 33 | impl 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 @@ | |||
1 | use std::thread; | ||
2 | use drop_bomb::DropBomb; | ||
3 | use Result; | ||
4 | |||
5 | pub struct ThreadWatcher { | ||
6 | name: &'static str, | ||
7 | thread: thread::JoinHandle<()>, | ||
8 | bomb: DropBomb, | ||
9 | } | ||
10 | |||
11 | impl 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 @@ | |||
1 | use std::{ | 1 | use std::{ |
2 | path::PathBuf, | 2 | path::PathBuf, |
3 | thread, | ||
4 | fs, | 3 | fs, |
5 | }; | 4 | }; |
6 | 5 | ||
7 | use crossbeam_channel::{Sender, Receiver, bounded}; | 6 | use crossbeam_channel::{Sender, Receiver, bounded}; |
8 | use drop_bomb::DropBomb; | ||
9 | use walkdir::WalkDir; | 7 | use walkdir::WalkDir; |
10 | 8 | ||
11 | use Result; | 9 | use { |
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 | ||
27 | pub struct Watcher { | 27 | pub fn watch(roots: Vec<PathBuf>) -> (Receiver<Vec<FileEvent>>, ThreadWatcher) { |
28 | thread: thread::JoinHandle<()>, | ||
29 | bomb: DropBomb, | ||
30 | } | ||
31 | |||
32 | impl 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 | |||
40 | pub 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 | ||
49 | fn run(roots: Vec<PathBuf>, sender: Sender<Vec<FileEvent>>) { | 33 | fn 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 @@ | |||
1 | extern crate tempdir; | 1 | #[macro_use] |
2 | extern crate crossbeam_channel; | 2 | extern crate crossbeam_channel; |
3 | extern crate tempdir; | ||
3 | extern crate languageserver_types; | 4 | extern crate languageserver_types; |
4 | extern crate serde; | 5 | extern crate serde; |
5 | extern crate serde_json; | 6 | extern crate serde_json; |
@@ -9,10 +10,12 @@ extern crate m; | |||
9 | 10 | ||
10 | mod support; | 11 | mod support; |
11 | 12 | ||
12 | use m::req::{Runnables, RunnablesParams}; | 13 | use m::req::{Runnables, RunnablesParams, DidReloadWorkspace}; |
13 | 14 | ||
14 | use support::project; | 15 | use support::project; |
15 | 16 | ||
17 | const LOG: &'static str = "WARN"; | ||
18 | |||
16 | #[test] | 19 | #[test] |
17 | fn test_runnables() { | 20 | fn 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] | ||
48 | fn test_project_model() { | ||
49 | let server = project(r#" | ||
50 | //- Cargo.toml | ||
51 | [package] | ||
52 | name = "foo" | ||
53 | version = "0.0.0" | ||
54 | |||
55 | //- src/lib.rs | ||
56 | pub 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 | ||
8 | use tempdir::TempDir; | 10 | use tempdir::TempDir; |
9 | use crossbeam_channel::{bounded, Sender, Receiver}; | 11 | use crossbeam_channel::{bounded, after, Sender, Receiver}; |
10 | use flexi_logger::Logger; | 12 | use flexi_logger::Logger; |
11 | use languageserver_types::{ | 13 | use 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}; | |||
23 | use m::{Result, main_loop}; | 25 | use m::{Result, main_loop}; |
24 | 26 | ||
25 | pub fn project(fixture: &str) -> Server { | 27 | pub 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)); |