1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
|
//! A minimal example LSP server that can only respond to the `gotoDefinition` request. To use
//! this example, execute it and then send an `initialize` request.
//!
//! ```no_run
//! Content-Length: 85
//!
//! {"jsonrpc": "2.0", "method": "initialize", "id": 1, "params": {"capabilities": {}}}
//! ```
//!
//! This will respond with a server respose. Then send it a `initialized` notification which will
//! have no response.
//!
//! ```no_run
//! Content-Length: 59
//!
//! {"jsonrpc": "2.0", "method": "initialized", "params": {}}
//! ```
//!
//! Once these two are sent, then we enter the main loop of the server. The only request this
//! example can handle is `gotoDefinition`:
//!
//! ```no_run
//! Content-Length: 159
//!
//! {"jsonrpc": "2.0", "method": "textDocument/definition", "id": 2, "params": {"textDocument": {"uri": "file://temp"}, "position": {"line": 1, "character": 1}}}
//! ```
//!
//! To finish up without errors, send a shutdown request:
//!
//! ```no_run
//! Content-Length: 67
//!
//! {"jsonrpc": "2.0", "method": "shutdown", "id": 3, "params": null}
//! ```
//!
//! The server will exit the main loop and finally we send a `shutdown` notification to stop
//! the server.
//!
//! ```
//! Content-Length: 54
//!
//! {"jsonrpc": "2.0", "method": "exit", "params": null}
//! ```
use std::error::Error;
use crossbeam_channel::{Receiver, Sender};
use gen_lsp_server::{
handle_shutdown, run_server, stdio_transport, RawMessage, RawRequest, RawResponse,
};
use log::info;
use lsp_types::{
request::{GotoDefinition, GotoDefinitionResponse},
InitializeParams, ServerCapabilities,
};
fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
// Set up logging. Because `stdio_transport` gets a lock on stdout and stdin, we must have
// our logging only write out to stderr.
flexi_logger::Logger::with_str("info").start().unwrap();
info!("starting generic LSP server");
// Create the transport. Includes the stdio (stdin and stdout) versions but this could
// also be implemented to use sockets or HTTP.
let (receiver, sender, io_threads) = stdio_transport();
// Run the server and wait for the two threads to end (typically by trigger LSP Exit event).
run_server(ServerCapabilities::default(), receiver, sender, main_loop)?;
io_threads.join()?;
// Shut down gracefully.
info!("shutting down server");
Ok(())
}
fn main_loop(
_params: InitializeParams,
receiver: &Receiver<RawMessage>,
sender: &Sender<RawMessage>,
) -> Result<(), Box<dyn Error + Sync + Send>> {
info!("starting example main loop");
for msg in receiver {
info!("got msg: {:?}", msg);
match msg {
RawMessage::Request(req) => {
let req = match log_handle_shutdown(req, sender) {
None => return Ok(()),
Some(req) => req,
};
info!("got request: {:?}", req);
match req.cast::<GotoDefinition>() {
Ok((id, params)) => {
info!("got gotoDefinition request #{}: {:?}", id, params);
let resp = RawResponse::ok::<GotoDefinition>(
id,
&Some(GotoDefinitionResponse::Array(Vec::new())),
);
info!("sending gotoDefinition response: {:?}", resp);
sender.send(RawMessage::Response(resp))?;
continue;
}
Err(req) => req,
};
// ...
}
RawMessage::Response(resp) => {
info!("got response: {:?}", resp);
}
RawMessage::Notification(not) => {
info!("got notification: {:?}", not);
}
}
}
Ok(())
}
pub fn log_handle_shutdown(req: RawRequest, sender: &Sender<RawMessage>) -> Option<RawRequest> {
info!("handle_shutdown: {:?}", req);
handle_shutdown(req, sender)
}
|