aboutsummaryrefslogtreecommitdiff
path: root/crates/ra_lsp_server
diff options
context:
space:
mode:
authorbors[bot] <26634292+bors[bot]@users.noreply.github.com>2019-08-30 18:39:19 +0100
committerGitHub <[email protected]>2019-08-30 18:39:19 +0100
commitf90a88630195a1b6b542b5883295d66dd6c59cef (patch)
tree9bdd597701e1b150492a78b6c85ff856f0fc51bd /crates/ra_lsp_server
parentffc95759c7b538d8dacc336912b62857d4919cdd (diff)
parent983de30a567f2cb4d9e28e12702e509ca713da62 (diff)
Merge #1740
1740: :arrow_up: lsp-server r=matklad a=matklad Co-authored-by: Aleksey Kladov <[email protected]>
Diffstat (limited to 'crates/ra_lsp_server')
-rw-r--r--crates/ra_lsp_server/Cargo.toml5
-rw-r--r--crates/ra_lsp_server/src/lib.rs1
-rw-r--r--crates/ra_lsp_server/src/main.rs74
-rw-r--r--crates/ra_lsp_server/src/main_loop.rs29
-rw-r--r--crates/ra_lsp_server/src/project_model.rs4
-rw-r--r--crates/ra_lsp_server/src/thread_worker.rs49
-rw-r--r--crates/ra_lsp_server/tests/heavy_tests/support.rs57
7 files changed, 132 insertions, 87 deletions
diff --git a/crates/ra_lsp_server/Cargo.toml b/crates/ra_lsp_server/Cargo.toml
index 084a20cd9..eb4812633 100644
--- a/crates/ra_lsp_server/Cargo.toml
+++ b/crates/ra_lsp_server/Cargo.toml
@@ -15,13 +15,12 @@ log = "0.4.3"
15lsp-types = { version = "0.60.0", features = ["proposed"] } 15lsp-types = { version = "0.60.0", features = ["proposed"] }
16rustc-hash = "1.0" 16rustc-hash = "1.0"
17parking_lot = "0.9.0" 17parking_lot = "0.9.0"
18 18jod-thread = "0.1.0"
19ra_vfs = "0.3.0" 19ra_vfs = "0.3.0"
20thread_worker = { path = "../thread_worker" }
21ra_syntax = { path = "../ra_syntax" } 20ra_syntax = { path = "../ra_syntax" }
22ra_text_edit = { path = "../ra_text_edit" } 21ra_text_edit = { path = "../ra_text_edit" }
23ra_ide_api = { path = "../ra_ide_api" } 22ra_ide_api = { path = "../ra_ide_api" }
24lsp-server = "0.1.0" 23lsp-server = "0.2.0"
25ra_project_model = { path = "../ra_project_model" } 24ra_project_model = { path = "../ra_project_model" }
26ra_prof = { path = "../ra_prof" } 25ra_prof = { path = "../ra_prof" }
27ra_vfs_glob = { path = "../ra_vfs_glob" } 26ra_vfs_glob = { path = "../ra_vfs_glob" }
diff --git a/crates/ra_lsp_server/src/lib.rs b/crates/ra_lsp_server/src/lib.rs
index 2c5d7c72d..69a577b3e 100644
--- a/crates/ra_lsp_server/src/lib.rs
+++ b/crates/ra_lsp_server/src/lib.rs
@@ -8,6 +8,7 @@ mod project_model;
8pub mod req; 8pub mod req;
9pub mod config; 9pub mod config;
10mod world; 10mod world;
11mod thread_worker;
11 12
12pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; 13pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
13pub use crate::{ 14pub use crate::{
diff --git a/crates/ra_lsp_server/src/main.rs b/crates/ra_lsp_server/src/main.rs
index 1debe7660..88504bb89 100644
--- a/crates/ra_lsp_server/src/main.rs
+++ b/crates/ra_lsp_server/src/main.rs
@@ -1,5 +1,5 @@
1use flexi_logger::{Duplicate, Logger}; 1use flexi_logger::{Duplicate, Logger};
2use lsp_server::{run_server, stdio_transport, LspServerError}; 2use lsp_server::Connection;
3 3
4use ra_lsp_server::{show_message, Result, ServerConfig}; 4use ra_lsp_server::{show_message, Result, ServerConfig};
5use ra_prof; 5use ra_prof;
@@ -29,46 +29,46 @@ fn main() -> Result<()> {
29} 29}
30 30
31fn main_inner() -> Result<()> { 31fn main_inner() -> Result<()> {
32 let (sender, receiver, io_threads) = stdio_transport();
33 let cwd = std::env::current_dir()?; 32 let cwd = std::env::current_dir()?;
34 let caps = serde_json::to_value(ra_lsp_server::server_capabilities()).unwrap(); 33 let (connection, io_threads) = Connection::stdio();
35 run_server(caps, sender, receiver, |params, s, r| { 34 let server_capabilities = serde_json::to_value(ra_lsp_server::server_capabilities()).unwrap();
36 let params: lsp_types::InitializeParams = serde_json::from_value(params)?;
37 let root = params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
38 35
39 let workspace_roots = params 36 let initialize_params = connection.initialize(server_capabilities)?;
40 .workspace_folders 37 let initialize_params: lsp_types::InitializeParams = serde_json::from_value(initialize_params)?;
41 .map(|workspaces| {
42 workspaces
43 .into_iter()
44 .filter_map(|it| it.uri.to_file_path().ok())
45 .collect::<Vec<_>>()
46 })
47 .filter(|workspaces| !workspaces.is_empty())
48 .unwrap_or_else(|| vec![root]);
49 38
50 let server_config: ServerConfig = params 39 let root = initialize_params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
51 .initialization_options 40
52 .and_then(|v| { 41 let workspace_roots = initialize_params
53 serde_json::from_value(v) 42 .workspace_folders
54 .map_err(|e| { 43 .map(|workspaces| {
55 log::error!("failed to deserialize config: {}", e); 44 workspaces.into_iter().filter_map(|it| it.uri.to_file_path().ok()).collect::<Vec<_>>()
56 show_message( 45 })
57 lsp_types::MessageType::Error, 46 .filter(|workspaces| !workspaces.is_empty())
58 format!("failed to deserialize config: {}", e), 47 .unwrap_or_else(|| vec![root]);
59 s, 48
60 ); 49 let server_config: ServerConfig = initialize_params
61 }) 50 .initialization_options
62 .ok() 51 .and_then(|v| {
63 }) 52 serde_json::from_value(v)
64 .unwrap_or_default(); 53 .map_err(|e| {
54 log::error!("failed to deserialize config: {}", e);
55 show_message(
56 lsp_types::MessageType::Error,
57 format!("failed to deserialize config: {}", e),
58 &connection.sender,
59 );
60 })
61 .ok()
62 })
63 .unwrap_or_default();
64
65 ra_lsp_server::main_loop(
66 workspace_roots,
67 initialize_params.capabilities,
68 server_config,
69 &connection,
70 )?;
65 71
66 ra_lsp_server::main_loop(workspace_roots, params.capabilities, server_config, r, s)
67 })
68 .map_err(|err| match err {
69 LspServerError::ProtocolError(err) => err.into(),
70 LspServerError::ServerError(err) => err,
71 })?;
72 log::info!("shutting down IO..."); 72 log::info!("shutting down IO...");
73 io_threads.join()?; 73 io_threads.join()?;
74 log::info!("... IO is down"); 74 log::info!("... IO is down");
diff --git a/crates/ra_lsp_server/src/main_loop.rs b/crates/ra_lsp_server/src/main_loop.rs
index fb357b36b..42ebb5cdf 100644
--- a/crates/ra_lsp_server/src/main_loop.rs
+++ b/crates/ra_lsp_server/src/main_loop.rs
@@ -5,7 +5,7 @@ pub(crate) mod pending_requests;
5use std::{error::Error, fmt, path::PathBuf, sync::Arc, time::Instant}; 5use std::{error::Error, fmt, path::PathBuf, sync::Arc, time::Instant};
6 6
7use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender}; 7use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender};
8use lsp_server::{handle_shutdown, ErrorCode, Message, Notification, Request, RequestId, Response}; 8use lsp_server::{Connection, ErrorCode, Message, Notification, Request, RequestId, Response};
9use lsp_types::{ClientCapabilities, NumberOrString}; 9use lsp_types::{ClientCapabilities, NumberOrString};
10use ra_ide_api::{Canceled, FeatureFlags, FileId, LibraryData}; 10use ra_ide_api::{Canceled, FeatureFlags, FileId, LibraryData};
11use ra_prof::profile; 11use ra_prof::profile;
@@ -51,8 +51,7 @@ pub fn main_loop(
51 ws_roots: Vec<PathBuf>, 51 ws_roots: Vec<PathBuf>,
52 client_caps: ClientCapabilities, 52 client_caps: ClientCapabilities,
53 config: ServerConfig, 53 config: ServerConfig,
54 msg_receiver: &Receiver<Message>, 54 connection: &Connection,
55 msg_sender: &Sender<Message>,
56) -> Result<()> { 55) -> Result<()> {
57 log::info!("server_config: {:#?}", config); 56 log::info!("server_config: {:#?}", config);
58 // FIXME: support dynamic workspace loading. 57 // FIXME: support dynamic workspace loading.
@@ -69,7 +68,7 @@ pub fn main_loop(
69 show_message( 68 show_message(
70 req::MessageType::Error, 69 req::MessageType::Error,
71 format!("rust-analyzer failed to load workspace: {}", e), 70 format!("rust-analyzer failed to load workspace: {}", e),
72 msg_sender, 71 &connection.sender,
73 ); 72 );
74 } 73 }
75 } 74 }
@@ -89,7 +88,7 @@ pub fn main_loop(
89 show_message( 88 show_message(
90 req::MessageType::Error, 89 req::MessageType::Error,
91 format!("unknown feature flag: {:?}", flag), 90 format!("unknown feature flag: {:?}", flag),
92 msg_sender, 91 &connection.sender,
93 ); 92 );
94 } 93 }
95 } 94 }
@@ -119,8 +118,7 @@ pub fn main_loop(
119 log::info!("server initialized, serving requests"); 118 log::info!("server initialized, serving requests");
120 let main_res = main_loop_inner( 119 let main_res = main_loop_inner(
121 &pool, 120 &pool,
122 msg_sender, 121 connection,
123 msg_receiver,
124 task_sender, 122 task_sender,
125 task_receiver.clone(), 123 task_receiver.clone(),
126 &mut state, 124 &mut state,
@@ -130,7 +128,7 @@ pub fn main_loop(
130 log::info!("waiting for tasks to finish..."); 128 log::info!("waiting for tasks to finish...");
131 task_receiver 129 task_receiver
132 .into_iter() 130 .into_iter()
133 .for_each(|task| on_task(task, msg_sender, &mut pending_requests, &mut state)); 131 .for_each(|task| on_task(task, &connection.sender, &mut pending_requests, &mut state));
134 log::info!("...tasks have finished"); 132 log::info!("...tasks have finished");
135 log::info!("joining threadpool..."); 133 log::info!("joining threadpool...");
136 drop(pool); 134 drop(pool);
@@ -196,8 +194,7 @@ impl fmt::Debug for Event {
196 194
197fn main_loop_inner( 195fn main_loop_inner(
198 pool: &ThreadPool, 196 pool: &ThreadPool,
199 msg_sender: &Sender<Message>, 197 connection: &Connection,
200 msg_receiver: &Receiver<Message>,
201 task_sender: Sender<Task>, 198 task_sender: Sender<Task>,
202 task_receiver: Receiver<Task>, 199 task_receiver: Receiver<Task>,
203 state: &mut WorldState, 200 state: &mut WorldState,
@@ -214,7 +211,7 @@ fn main_loop_inner(
214 loop { 211 loop {
215 log::trace!("selecting"); 212 log::trace!("selecting");
216 let event = select! { 213 let event = select! {
217 recv(msg_receiver) -> msg => match msg { 214 recv(&connection.receiver) -> msg => match msg {
218 Ok(msg) => Event::Msg(msg), 215 Ok(msg) => Event::Msg(msg),
219 Err(RecvError) => Err("client exited without shutdown")?, 216 Err(RecvError) => Err("client exited without shutdown")?,
220 }, 217 },
@@ -238,7 +235,7 @@ fn main_loop_inner(
238 let mut state_changed = false; 235 let mut state_changed = false;
239 match event { 236 match event {
240 Event::Task(task) => { 237 Event::Task(task) => {
241 on_task(task, msg_sender, pending_requests, state); 238 on_task(task, &connection.sender, pending_requests, state);
242 state.maybe_collect_garbage(); 239 state.maybe_collect_garbage();
243 } 240 }
244 Event::Vfs(task) => { 241 Event::Vfs(task) => {
@@ -252,7 +249,7 @@ fn main_loop_inner(
252 } 249 }
253 Event::Msg(msg) => match msg { 250 Event::Msg(msg) => match msg {
254 Message::Request(req) => { 251 Message::Request(req) => {
255 if handle_shutdown(&req, msg_sender) { 252 if connection.handle_shutdown(&req)? {
256 return Ok(()); 253 return Ok(());
257 }; 254 };
258 on_request( 255 on_request(
@@ -260,13 +257,13 @@ fn main_loop_inner(
260 pending_requests, 257 pending_requests,
261 pool, 258 pool,
262 &task_sender, 259 &task_sender,
263 msg_sender, 260 &connection.sender,
264 loop_start, 261 loop_start,
265 req, 262 req,
266 )? 263 )?
267 } 264 }
268 Message::Notification(not) => { 265 Message::Notification(not) => {
269 on_notification(msg_sender, state, pending_requests, &mut subs, not)?; 266 on_notification(&connection.sender, state, pending_requests, &mut subs, not)?;
270 state_changed = true; 267 state_changed = true;
271 } 268 }
272 Message::Response(resp) => log::error!("unexpected response: {:?}", resp), 269 Message::Response(resp) => log::error!("unexpected response: {:?}", resp),
@@ -294,7 +291,7 @@ fn main_loop_inner(
294 let n_packages: usize = state.workspaces.iter().map(|it| it.n_packages()).sum(); 291 let n_packages: usize = state.workspaces.iter().map(|it| it.n_packages()).sum();
295 if state.feature_flags().get("notifications.workspace-loaded") { 292 if state.feature_flags().get("notifications.workspace-loaded") {
296 let msg = format!("workspace loaded, {} rust packages", n_packages); 293 let msg = format!("workspace loaded, {} rust packages", n_packages);
297 show_message(req::MessageType::Info, msg, msg_sender); 294 show_message(req::MessageType::Info, msg, &connection.sender);
298 } 295 }
299 // Only send the notification first time 296 // Only send the notification first time
300 send_workspace_notification = false; 297 send_workspace_notification = false;
diff --git a/crates/ra_lsp_server/src/project_model.rs b/crates/ra_lsp_server/src/project_model.rs
index ad59cde64..6234563f2 100644
--- a/crates/ra_lsp_server/src/project_model.rs
+++ b/crates/ra_lsp_server/src/project_model.rs
@@ -1,8 +1,6 @@
1use std::path::PathBuf; 1use std::path::PathBuf;
2 2
3use thread_worker::Worker; 3use crate::{thread_worker::Worker, Result};
4
5use crate::Result;
6 4
7pub use ra_project_model::{ 5pub use ra_project_model::{
8 CargoWorkspace, Package, ProjectWorkspace, Sysroot, Target, TargetKind, 6 CargoWorkspace, Package, ProjectWorkspace, Sysroot, Target, TargetKind,
diff --git a/crates/ra_lsp_server/src/thread_worker.rs b/crates/ra_lsp_server/src/thread_worker.rs
new file mode 100644
index 000000000..68e5c124d
--- /dev/null
+++ b/crates/ra_lsp_server/src/thread_worker.rs
@@ -0,0 +1,49 @@
1//! Small utility to correctly spawn crossbeam-channel based worker threads.
2
3use crossbeam_channel::{bounded, unbounded, Receiver, Sender};
4
5/// A wrapper around event-processing thread with automatic shutdown semantics.
6pub struct Worker<I, O> {
7 // XXX: field order is significant here.
8 //
9 // In Rust, fields are dropped in the declaration order, and we rely on this
10 // here. We must close input first, so that the `thread` (who holds the
11 // opposite side of the channel) noticed shutdown. Then, we must join the
12 // thread, but we must keep out alive so that the thread does not panic.
13 //
14 // Note that a potential problem here is that we might drop some messages
15 // from receiver on the floor. This is ok for rust-analyzer: we have only a
16 // single client, so, if we are shutting down, nobody is interested in the
17 // unfinished work anyway!
18 sender: Sender<I>,
19 _thread: jod_thread::JoinHandle<()>,
20 receiver: Receiver<O>,
21}
22
23impl<I, O> Worker<I, O> {
24 pub fn spawn<F>(name: &'static str, buf: usize, f: F) -> Worker<I, O>
25 where
26 F: FnOnce(Receiver<I>, Sender<O>) + Send + 'static,
27 I: Send + 'static,
28 O: Send + 'static,
29 {
30 // Set up worker channels in a deadlock-avoiding way. If one sets both input
31 // and output buffers to a fixed size, a worker might get stuck.
32 let (sender, input_receiver) = bounded::<I>(buf);
33 let (output_sender, receiver) = unbounded::<O>();
34 let _thread = jod_thread::Builder::new()
35 .name(name.to_string())
36 .spawn(move || f(input_receiver, output_sender))
37 .expect("failed to spawn a thread");
38 Worker { sender, _thread, receiver }
39 }
40}
41
42impl<I, O> Worker<I, O> {
43 pub fn sender(&self) -> &Sender<I> {
44 &self.sender
45 }
46 pub fn receiver(&self) -> &Receiver<O> {
47 &self.receiver
48 }
49}
diff --git a/crates/ra_lsp_server/tests/heavy_tests/support.rs b/crates/ra_lsp_server/tests/heavy_tests/support.rs
index 45b4cacf6..89f65cef4 100644
--- a/crates/ra_lsp_server/tests/heavy_tests/support.rs
+++ b/crates/ra_lsp_server/tests/heavy_tests/support.rs
@@ -8,16 +8,17 @@ use std::{
8 8
9use crossbeam_channel::{after, select, Receiver}; 9use crossbeam_channel::{after, select, Receiver};
10use flexi_logger::Logger; 10use flexi_logger::Logger;
11use lsp_server::{Message, Notification, Request}; 11use lsp_server::{Connection, Message, Notification, Request};
12use lsp_types::{ 12use lsp_types::{
13 request::Shutdown, ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, 13 notification::{DidOpenTextDocument, Exit},
14 TextDocumentClientCapabilities, TextDocumentIdentifier, TextDocumentItem, Url, 14 request::Shutdown,
15 ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities,
16 TextDocumentIdentifier, TextDocumentItem, Url,
15}; 17};
16use serde::Serialize; 18use serde::Serialize;
17use serde_json::{to_string_pretty, Value}; 19use serde_json::{to_string_pretty, Value};
18use tempfile::TempDir; 20use tempfile::TempDir;
19use test_utils::{find_mismatch, parse_fixture}; 21use test_utils::{find_mismatch, parse_fixture};
20use thread_worker::Worker;
21 22
22use ra_lsp_server::{main_loop, req, ServerConfig}; 23use ra_lsp_server::{main_loop, req, ServerConfig};
23 24
@@ -83,7 +84,8 @@ pub struct Server {
83 req_id: Cell<u64>, 84 req_id: Cell<u64>,
84 messages: RefCell<Vec<Message>>, 85 messages: RefCell<Vec<Message>>,
85 dir: TempDir, 86 dir: TempDir,
86 worker: Worker<Message, Message>, 87 _thread: jod_thread::JoinHandle<()>,
88 client: Connection,
87} 89}
88 90
89impl Server { 91impl Server {
@@ -96,11 +98,11 @@ impl Server {
96 let path = dir.path().to_path_buf(); 98 let path = dir.path().to_path_buf();
97 99
98 let roots = if roots.is_empty() { vec![path] } else { roots }; 100 let roots = if roots.is_empty() { vec![path] } else { roots };
101 let (connection, client) = Connection::memory();
99 102
100 let worker = Worker::<Message, Message>::spawn( 103 let _thread = jod_thread::Builder::new()
101 "test server", 104 .name("test server".to_string())
102 128, 105 .spawn(move || {
103 move |msg_receiver, msg_sender| {
104 main_loop( 106 main_loop(
105 roots, 107 roots,
106 ClientCapabilities { 108 ClientCapabilities {
@@ -116,26 +118,24 @@ impl Server {
116 experimental: None, 118 experimental: None,
117 }, 119 },
118 ServerConfig { with_sysroot, ..ServerConfig::default() }, 120 ServerConfig { with_sysroot, ..ServerConfig::default() },
119 &msg_receiver, 121 &connection,
120 &msg_sender,
121 ) 122 )
122 .unwrap() 123 .unwrap()
123 }, 124 })
124 ); 125 .expect("failed to spawn a thread");
125 let res = Server { req_id: Cell::new(1), dir, messages: Default::default(), worker }; 126
127 let res =
128 Server { req_id: Cell::new(1), dir, messages: Default::default(), client, _thread };
126 129
127 for (path, text) in files { 130 for (path, text) in files {
128 res.send_notification(Notification::new( 131 res.notification::<DidOpenTextDocument>(DidOpenTextDocumentParams {
129 "textDocument/didOpen".to_string(), 132 text_document: TextDocumentItem {
130 &DidOpenTextDocumentParams { 133 uri: Url::from_file_path(path).unwrap(),
131 text_document: TextDocumentItem { 134 language_id: "rust".to_string(),
132 uri: Url::from_file_path(path).unwrap(), 135 version: 0,
133 language_id: "rust".to_string(), 136 text,
134 version: 0,
135 text,
136 },
137 }, 137 },
138 )) 138 })
139 } 139 }
140 res 140 res
141 } 141 }
@@ -184,7 +184,7 @@ impl Server {
184 } 184 }
185 fn send_request_(&self, r: Request) -> Value { 185 fn send_request_(&self, r: Request) -> Value {
186 let id = r.id.clone(); 186 let id = r.id.clone();
187 self.worker.sender().send(r.into()).unwrap(); 187 self.client.sender.send(r.into()).unwrap();
188 while let Some(msg) = self.recv() { 188 while let Some(msg) = self.recv() {
189 match msg { 189 match msg {
190 Message::Request(req) => panic!("unexpected request: {:?}", req), 190 Message::Request(req) => panic!("unexpected request: {:?}", req),
@@ -225,13 +225,13 @@ impl Server {
225 } 225 }
226 } 226 }
227 fn recv(&self) -> Option<Message> { 227 fn recv(&self) -> Option<Message> {
228 recv_timeout(&self.worker.receiver()).map(|msg| { 228 recv_timeout(&self.client.receiver).map(|msg| {
229 self.messages.borrow_mut().push(msg.clone()); 229 self.messages.borrow_mut().push(msg.clone());
230 msg 230 msg
231 }) 231 })
232 } 232 }
233 fn send_notification(&self, not: Notification) { 233 fn send_notification(&self, not: Notification) {
234 self.worker.sender().send(Message::Notification(not)).unwrap(); 234 self.client.sender.send(Message::Notification(not)).unwrap();
235 } 235 }
236 236
237 pub fn path(&self) -> &Path { 237 pub fn path(&self) -> &Path {
@@ -241,7 +241,8 @@ impl Server {
241 241
242impl Drop for Server { 242impl Drop for Server {
243 fn drop(&mut self) { 243 fn drop(&mut self) {
244 self.send_request::<Shutdown>(()); 244 self.request::<Shutdown>((), Value::Null);
245 self.notification::<Exit>(());
245 } 246 }
246} 247}
247 248