lsp_server/lib.rs
1//! A language server scaffold, exposing a synchronous crossbeam-channel based API.
2//! This crate handles protocol handshaking and parsing messages, while you
3//! control the message dispatch loop yourself.
4//!
5//! Run with `RUST_LOG=lsp_server=debug` to see all the messages.
6
7#![warn(rust_2018_idioms, unused_lifetimes)]
8#![allow(clippy::print_stdout, clippy::disallowed_types)]
9
10mod error;
11mod msg;
12mod req_queue;
13mod socket;
14mod stdio;
15
16use std::{
17 io,
18 net::{TcpListener, TcpStream, ToSocketAddrs},
19};
20
21use crossbeam_channel::{Receiver, RecvError, RecvTimeoutError, Sender};
22
23pub use crate::{
24 error::{ExtractError, ProtocolError},
25 msg::{
26 ErrorCode, Message, Notification, Request, RequestId, Response, ResponseError, ResponseKind,
27 },
28 req_queue::{Incoming, Outgoing, ReqQueue},
29 stdio::IoThreads,
30};
31
32/// Connection is just a pair of channels of LSP messages.
33pub struct Connection {
34 pub sender: Sender<Message>,
35 pub receiver: Receiver<Message>,
36}
37
38impl Connection {
39 /// Create connection over standard in/standard out.
40 ///
41 /// Use this to create a real language server.
42 pub fn stdio() -> (Connection, IoThreads) {
43 let (sender, receiver, io_threads) = stdio::stdio_transport();
44 (Connection { sender, receiver }, io_threads)
45 }
46
47 /// Open a connection over tcp.
48 /// This call blocks until a connection is established.
49 ///
50 /// Use this to create a real language server.
51 pub fn connect<A: ToSocketAddrs>(addr: A) -> io::Result<(Connection, IoThreads)> {
52 let stream = TcpStream::connect(addr)?;
53 let (sender, receiver, io_threads) = socket::socket_transport(stream);
54 Ok((Connection { sender, receiver }, io_threads))
55 }
56
57 /// Listen for a connection over tcp.
58 /// This call blocks until a connection is established.
59 ///
60 /// Use this to create a real language server.
61 pub fn listen<A: ToSocketAddrs>(addr: A) -> io::Result<(Connection, IoThreads)> {
62 let listener = TcpListener::bind(addr)?;
63 let (stream, _) = listener.accept()?;
64 let (sender, receiver, io_threads) = socket::socket_transport(stream);
65 Ok((Connection { sender, receiver }, io_threads))
66 }
67
68 /// Creates a pair of connected connections.
69 ///
70 /// Use this for testing.
71 pub fn memory() -> (Connection, Connection) {
72 let (s1, r1) = crossbeam_channel::unbounded();
73 let (s2, r2) = crossbeam_channel::unbounded();
74 (Connection { sender: s1, receiver: r2 }, Connection { sender: s2, receiver: r1 })
75 }
76
77 /// Starts the initialization process by waiting for an initialize
78 /// request from the client. Use this for more advanced customization than
79 /// `initialize` can provide.
80 ///
81 /// Returns the request id and serialized `InitializeParams` from the client.
82 ///
83 /// # Example
84 ///
85 /// ```no_run
86 /// use std::error::Error;
87 /// use lsp_types::{ClientCapabilities, InitializeParams, ServerCapabilities};
88 ///
89 /// use lsp_server::{Connection, Message, Request, RequestId, Response};
90 ///
91 /// fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
92 /// // Create the transport. Includes the stdio (stdin and stdout) versions but this could
93 /// // also be implemented to use sockets or HTTP.
94 /// let (connection, io_threads) = Connection::stdio();
95 ///
96 /// // Run the server
97 /// let (id, params) = connection.initialize_start()?;
98 ///
99 /// let init_params: InitializeParams = serde_json::from_value(params).unwrap();
100 /// let client_capabilities: ClientCapabilities = init_params.capabilities;
101 /// let server_capabilities = ServerCapabilities::default();
102 ///
103 /// let initialize_data = serde_json::json!({
104 /// "capabilities": server_capabilities,
105 /// "serverInfo": {
106 /// "name": "lsp-server-test",
107 /// "version": "0.1"
108 /// }
109 /// });
110 ///
111 /// connection.initialize_finish(id, initialize_data)?;
112 ///
113 /// // ... Run main loop ...
114 ///
115 /// Ok(())
116 /// }
117 /// ```
118 pub fn initialize_start(&self) -> Result<(RequestId, serde_json::Value), ProtocolError> {
119 self.initialize_start_while(|| true)
120 }
121
122 /// Starts the initialization process by waiting for an initialize as described in
123 /// [`Self::initialize_start`] as long as `running` returns
124 /// `true` while the return value can be changed through a sig handler such as `CTRL + C`.
125 ///
126 /// # Example
127 ///
128 /// ```rust
129 /// use std::sync::atomic::{AtomicBool, Ordering};
130 /// use std::sync::Arc;
131 /// # use std::error::Error;
132 /// # use lsp_types::{ClientCapabilities, InitializeParams, ServerCapabilities};
133 /// # use lsp_server::{Connection, Message, Request, RequestId, Response};
134 /// # fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
135 /// let running = Arc::new(AtomicBool::new(true));
136 /// # running.store(true, Ordering::SeqCst);
137 /// let r = running.clone();
138 ///
139 /// ctrlc::set_handler(move || {
140 /// r.store(false, Ordering::SeqCst);
141 /// }).expect("Error setting Ctrl-C handler");
142 ///
143 /// let (connection, io_threads) = Connection::stdio();
144 ///
145 /// let res = connection.initialize_start_while(|| running.load(Ordering::SeqCst));
146 /// # assert!(res.is_err());
147 ///
148 /// # Ok(())
149 /// # }
150 /// ```
151 pub fn initialize_start_while<C>(
152 &self,
153 running: C,
154 ) -> Result<(RequestId, serde_json::Value), ProtocolError>
155 where
156 C: Fn() -> bool,
157 {
158 while running() {
159 let msg = match self.receiver.recv_timeout(std::time::Duration::from_secs(1)) {
160 Ok(msg) => msg,
161 Err(RecvTimeoutError::Timeout) => {
162 continue;
163 }
164 Err(RecvTimeoutError::Disconnected) => return Err(ProtocolError::disconnected()),
165 };
166
167 match msg {
168 Message::Request(req) if req.is_initialize() => return Ok((req.id, req.params)),
169 // Respond to non-initialize requests with ServerNotInitialized
170 Message::Request(req) => {
171 let resp = Response::new_err(
172 req.id.clone(),
173 ErrorCode::ServerNotInitialized as i32,
174 format!("expected initialize request, got {req:?}"),
175 );
176 self.sender.send(resp.into()).unwrap();
177 continue;
178 }
179 Message::Notification(n) if !n.is_exit() => {
180 continue;
181 }
182 msg => {
183 return Err(ProtocolError::new(format!(
184 "expected initialize request, got {msg:?}"
185 )));
186 }
187 };
188 }
189
190 Err(ProtocolError::new(String::from(
191 "Initialization has been aborted during initialization",
192 )))
193 }
194
195 /// Finishes the initialization process by sending an `InitializeResult` to the client
196 pub fn initialize_finish(
197 &self,
198 initialize_id: RequestId,
199 initialize_result: serde_json::Value,
200 ) -> Result<(), ProtocolError> {
201 let resp = Response::new_ok(initialize_id, initialize_result);
202 self.sender.send(resp.into()).unwrap();
203 match &self.receiver.recv() {
204 Ok(Message::Notification(n)) if n.is_initialized() => Ok(()),
205 Ok(msg) => Err(ProtocolError::new(format!(
206 r#"expected initialized notification, got: {msg:?}"#
207 ))),
208 Err(RecvError) => Err(ProtocolError::disconnected()),
209 }
210 }
211
212 /// Finishes the initialization process as described in [`Self::initialize_finish`] as
213 /// long as `running` returns `true` while the return value can be changed through a sig
214 /// handler such as `CTRL + C`.
215 pub fn initialize_finish_while<C>(
216 &self,
217 initialize_id: RequestId,
218 initialize_result: serde_json::Value,
219 running: C,
220 ) -> Result<(), ProtocolError>
221 where
222 C: Fn() -> bool,
223 {
224 let resp = Response::new_ok(initialize_id, initialize_result);
225 self.sender.send(resp.into()).unwrap();
226
227 while running() {
228 let msg = match self.receiver.recv_timeout(std::time::Duration::from_secs(1)) {
229 Ok(msg) => msg,
230 Err(RecvTimeoutError::Timeout) => {
231 continue;
232 }
233 Err(RecvTimeoutError::Disconnected) => {
234 return Err(ProtocolError::disconnected());
235 }
236 };
237
238 match msg {
239 Message::Notification(n) if n.is_initialized() => {
240 return Ok(());
241 }
242 msg => {
243 return Err(ProtocolError::new(format!(
244 r#"expected initialized notification, got: {msg:?}"#
245 )));
246 }
247 }
248 }
249
250 Err(ProtocolError::new(String::from(
251 "Initialization has been aborted during initialization",
252 )))
253 }
254
255 /// Initialize the connection. Sends the server capabilities
256 /// to the client and returns the serialized client capabilities
257 /// on success. If more fine-grained initialization is required use
258 /// `initialize_start`/`initialize_finish`.
259 ///
260 /// # Example
261 ///
262 /// ```no_run
263 /// use std::error::Error;
264 /// use lsp_types::ServerCapabilities;
265 ///
266 /// use lsp_server::{Connection, Message, Request, RequestId, Response};
267 ///
268 /// fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
269 /// // Create the transport. Includes the stdio (stdin and stdout) versions but this could
270 /// // also be implemented to use sockets or HTTP.
271 /// let (connection, io_threads) = Connection::stdio();
272 ///
273 /// // Run the server
274 /// let server_capabilities = serde_json::to_value(&ServerCapabilities::default()).unwrap();
275 /// let initialization_params = connection.initialize(server_capabilities)?;
276 ///
277 /// // ... Run main loop ...
278 ///
279 /// Ok(())
280 /// }
281 /// ```
282 pub fn initialize(
283 &self,
284 server_capabilities: serde_json::Value,
285 ) -> Result<serde_json::Value, ProtocolError> {
286 let (id, params) = self.initialize_start()?;
287
288 let initialize_data = serde_json::json!({
289 "capabilities": server_capabilities,
290 });
291
292 self.initialize_finish(id, initialize_data)?;
293
294 Ok(params)
295 }
296
297 /// Initialize the connection as described in [`Self::initialize`] as long as `running` returns
298 /// `true` while the return value can be changed through a sig handler such as `CTRL + C`.
299 ///
300 /// # Example
301 ///
302 /// ```rust
303 /// use std::sync::atomic::{AtomicBool, Ordering};
304 /// use std::sync::Arc;
305 /// # use std::error::Error;
306 /// # use lsp_types::ServerCapabilities;
307 /// # use lsp_server::{Connection, Message, Request, RequestId, Response};
308 ///
309 /// # fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
310 /// let running = Arc::new(AtomicBool::new(true));
311 /// # running.store(true, Ordering::SeqCst);
312 /// let r = running.clone();
313 ///
314 /// ctrlc::set_handler(move || {
315 /// r.store(false, Ordering::SeqCst);
316 /// }).expect("Error setting Ctrl-C handler");
317 ///
318 /// let (connection, io_threads) = Connection::stdio();
319 ///
320 /// let server_capabilities = serde_json::to_value(&ServerCapabilities::default()).unwrap();
321 /// let initialization_params = connection.initialize_while(
322 /// server_capabilities,
323 /// || running.load(Ordering::SeqCst)
324 /// );
325 ///
326 /// # assert!(initialization_params.is_err());
327 /// # Ok(())
328 /// # }
329 /// ```
330 pub fn initialize_while<C>(
331 &self,
332 server_capabilities: serde_json::Value,
333 running: C,
334 ) -> Result<serde_json::Value, ProtocolError>
335 where
336 C: Fn() -> bool,
337 {
338 let (id, params) = self.initialize_start_while(&running)?;
339
340 let initialize_data = serde_json::json!({
341 "capabilities": server_capabilities,
342 });
343
344 self.initialize_finish_while(id, initialize_data, running)?;
345
346 Ok(params)
347 }
348
349 /// If `req` is `Shutdown`, respond to it and return `true`, otherwise return `false`
350 pub fn handle_shutdown(&self, req: &Request) -> Result<bool, ProtocolError> {
351 if !req.is_shutdown() {
352 return Ok(false);
353 }
354 let resp = Response::new_ok(req.id.clone(), ());
355 let _ = self.sender.send(resp.into());
356 match &self.receiver.recv_timeout(std::time::Duration::from_secs(30)) {
357 Ok(Message::Notification(n)) if n.is_exit() => (),
358 Ok(msg) => {
359 return Err(ProtocolError::new(format!(
360 "unexpected message during shutdown: {msg:?}"
361 )));
362 }
363 Err(RecvTimeoutError::Timeout) => {
364 return Err(ProtocolError::new(
365 "timed out waiting for exit notification".to_owned(),
366 ));
367 }
368 Err(RecvTimeoutError::Disconnected) => {
369 return Err(ProtocolError::new(
370 "channel disconnected waiting for exit notification".to_owned(),
371 ));
372 }
373 }
374 Ok(true)
375 }
376}
377
378#[cfg(test)]
379mod tests {
380 use crossbeam_channel::unbounded;
381 use lsp_types::{
382 ExitNotification, InitializeParams, InitializeRequest, InitializedNotification,
383 InitializedParams, Notification as _, Request as _,
384 };
385 use serde_json::to_value;
386
387 use crate::{Connection, Message, ProtocolError, RequestId};
388
389 struct TestCase {
390 test_messages: Vec<Message>,
391 expected_resp: Result<(RequestId, serde_json::Value), ProtocolError>,
392 }
393
394 fn initialize_start_test(test_case: TestCase) {
395 let (reader_sender, reader_receiver) = unbounded::<Message>();
396 let (writer_sender, writer_receiver) = unbounded::<Message>();
397 let conn = Connection { sender: writer_sender, receiver: reader_receiver };
398
399 for msg in test_case.test_messages {
400 assert!(reader_sender.send(msg).is_ok());
401 }
402
403 let resp = conn.initialize_start();
404 assert_eq!(test_case.expected_resp, resp);
405
406 assert!(writer_receiver.recv_timeout(std::time::Duration::from_secs(1)).is_err());
407 }
408
409 #[test]
410 fn not_exit_notification() {
411 let notification = crate::Notification {
412 method: InitializedNotification::METHOD.to_string(),
413 params: to_value(InitializedParams {}).unwrap(),
414 };
415
416 let params_as_value = to_value(InitializeParams::default()).unwrap();
417 let req_id = RequestId::from(234);
418 let request = crate::Request {
419 id: req_id.clone(),
420 method: InitializeRequest::METHOD.to_string(),
421 params: params_as_value.clone(),
422 };
423
424 initialize_start_test(TestCase {
425 test_messages: vec![notification.into(), request.into()],
426 expected_resp: Ok((req_id, params_as_value)),
427 });
428 }
429
430 #[test]
431 fn exit_notification() {
432 let notification = crate::Notification {
433 method: ExitNotification::METHOD.to_string(),
434 params: to_value(()).unwrap(),
435 };
436 let notification_msg = Message::from(notification);
437
438 initialize_start_test(TestCase {
439 test_messages: vec![notification_msg.clone()],
440 expected_resp: Err(ProtocolError::new(format!(
441 "expected initialize request, got {notification_msg:?}"
442 ))),
443 });
444 }
445}