mirror of
https://github.com/rust-lang/rust-analyzer
synced 2025-01-12 05:08:52 +00:00
move lsp-server to a separate repository
This commit is contained in:
parent
7d72ca8000
commit
72a3722470
14 changed files with 141 additions and 725 deletions
46
Cargo.lock
generated
46
Cargo.lock
generated
|
@ -214,23 +214,6 @@ dependencies = [
|
||||||
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"bitflags 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "console"
|
|
||||||
version = "0.7.7"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
dependencies = [
|
|
||||||
"atty 0.2.13 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"clicolors-control 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"encode_unicode 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"libc 0.2.62 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"regex 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"termios 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"unicode-width 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"winapi 0.3.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "console"
|
name = "console"
|
||||||
version = "0.8.0"
|
version = "0.8.0"
|
||||||
|
@ -472,18 +455,6 @@ name = "fuchsia-zircon-sys"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "gen_lsp_server"
|
|
||||||
version = "0.2.0"
|
|
||||||
dependencies = [
|
|
||||||
"crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"flexi_logger 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"lsp-types 0.60.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.1.10"
|
version = "0.1.10"
|
||||||
|
@ -539,7 +510,7 @@ name = "indicatif"
|
||||||
version = "0.11.0"
|
version = "0.11.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)",
|
"console 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"number_prefix 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -689,6 +660,17 @@ dependencies = [
|
||||||
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"cfg-if 0.1.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "lsp-server"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde 1.0.99 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"serde_json 1.0.40 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "lsp-types"
|
name = "lsp-types"
|
||||||
version = "0.60.0"
|
version = "0.60.0"
|
||||||
|
@ -1092,8 +1074,8 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
"crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"flexi_logger 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
"flexi_logger 0.14.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"gen_lsp_server 0.2.0",
|
|
||||||
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
"log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"lsp-server 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"lsp-types 0.60.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"lsp-types 0.60.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"ra_ide_api 0.1.0",
|
"ra_ide_api 0.1.0",
|
||||||
|
@ -1899,7 +1881,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
|
"checksum clap 2.33.0 (registry+https://github.com/rust-lang/crates.io-index)" = "5067f5bb2d80ef5d68b4c87db81601f0b75bca627bc2ef76b141d7b846a3c6d9"
|
||||||
"checksum clicolors-control 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73abfd4c73d003a674ce5d2933fca6ce6c42480ea84a5ffe0a2dc39ed56300f9"
|
"checksum clicolors-control 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "73abfd4c73d003a674ce5d2933fca6ce6c42480ea84a5ffe0a2dc39ed56300f9"
|
||||||
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f"
|
||||||
"checksum console 0.7.7 (registry+https://github.com/rust-lang/crates.io-index)" = "8ca57c2c14b8a2bf3105bc9d15574aad80babf6a9c44b1058034cdf8bd169628"
|
|
||||||
"checksum console 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b147390a412132d75d10dd3b7b175a69cf5fd95032f7503c7091b8831ba10242"
|
"checksum console 0.8.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b147390a412132d75d10dd3b7b175a69cf5fd95032f7503c7091b8831ba10242"
|
||||||
"checksum crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2d818a4990769aac0c7ff1360e233ef3a41adcb009ebb2036bf6915eb0f6b23c"
|
"checksum crossbeam 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "2d818a4990769aac0c7ff1360e233ef3a41adcb009ebb2036bf6915eb0f6b23c"
|
||||||
"checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa"
|
"checksum crossbeam-channel 0.3.9 (registry+https://github.com/rust-lang/crates.io-index)" = "c8ec7fcd21571dc78f96cc96243cab8d8f035247c3efd16c687be154c3fa9efa"
|
||||||
|
@ -1956,6 +1937,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
|
"checksum linked-hash-map 0.5.2 (registry+https://github.com/rust-lang/crates.io-index)" = "ae91b68aebc4ddb91978b11a1b02ddd8602a05ec19002801c5666000e05e0f83"
|
||||||
"checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc"
|
"checksum lock_api 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "f8912e782533a93a167888781b836336a6ca5da6175c05944c86cf28c31104dc"
|
||||||
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
"checksum log 0.4.8 (registry+https://github.com/rust-lang/crates.io-index)" = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7"
|
||||||
|
"checksum lsp-server 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "47632ec528046c1a39f14448f1ee7d66d4b7b83e1771590b62e6c08665dea053"
|
||||||
"checksum lsp-types 0.60.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fe3edefcd66dde1f7f1df706f46520a3c93adc5ca4bc5747da6621195e894efd"
|
"checksum lsp-types 0.60.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fe3edefcd66dde1f7f1df706f46520a3c93adc5ca4bc5747da6621195e894efd"
|
||||||
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
"checksum matches 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "7ffc5c5338469d4d3ea17d269fa8ea3512ad247247c30bd2df69e68309ed0a08"
|
||||||
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
|
"checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e"
|
||||||
|
|
|
@ -1,18 +0,0 @@
|
||||||
[package]
|
|
||||||
edition = "2018"
|
|
||||||
name = "gen_lsp_server"
|
|
||||||
version = "0.2.0"
|
|
||||||
authors = ["rust-analyzer developers"]
|
|
||||||
repository = "https://github.com/rust-analyzer/rust-analyzer"
|
|
||||||
license = "MIT OR Apache-2.0"
|
|
||||||
description = "Generic LSP server scaffold."
|
|
||||||
|
|
||||||
[dependencies]
|
|
||||||
lsp-types = "0.60.0"
|
|
||||||
log = "0.4.3"
|
|
||||||
serde_json = "1.0.34"
|
|
||||||
serde = { version = "1.0.83", features = ["derive"] }
|
|
||||||
crossbeam-channel = "0.3.5"
|
|
||||||
|
|
||||||
[dev-dependencies]
|
|
||||||
flexi_logger = "0.14.0"
|
|
|
@ -1,47 +0,0 @@
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
|
||||||
use gen_lsp_server::{handle_shutdown, run_server, stdio_transport, RawMessage, RawResponse};
|
|
||||||
use lsp_types::{
|
|
||||||
request::{GotoDefinition, GotoDefinitionResponse},
|
|
||||||
InitializeParams, ServerCapabilities,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn main() -> Result<(), Box<dyn Error + Sync + Send>> {
|
|
||||||
let (receiver, sender, io_threads) = stdio_transport();
|
|
||||||
run_server(ServerCapabilities::default(), receiver, sender, main_loop)?;
|
|
||||||
io_threads.join()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn main_loop(
|
|
||||||
_params: InitializeParams,
|
|
||||||
receiver: &Receiver<RawMessage>,
|
|
||||||
sender: &Sender<RawMessage>,
|
|
||||||
) -> Result<(), Box<dyn Error + Sync + Send>> {
|
|
||||||
for msg in receiver {
|
|
||||||
match msg {
|
|
||||||
RawMessage::Request(req) => {
|
|
||||||
let req = match handle_shutdown(req, sender) {
|
|
||||||
None => return Ok(()),
|
|
||||||
Some(req) => req,
|
|
||||||
};
|
|
||||||
match req.cast::<GotoDefinition>() {
|
|
||||||
Ok((id, _params)) => {
|
|
||||||
let resp = RawResponse::ok::<GotoDefinition>(
|
|
||||||
id,
|
|
||||||
&Some(GotoDefinitionResponse::Array(Vec::new())),
|
|
||||||
);
|
|
||||||
sender.send(RawMessage::Response(resp))?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
Err(req) => req,
|
|
||||||
};
|
|
||||||
// ...
|
|
||||||
}
|
|
||||||
RawMessage::Response(_resp) => (),
|
|
||||||
RawMessage::Notification(_not) => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,120 +0,0 @@
|
||||||
//! 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)
|
|
||||||
}
|
|
|
@ -1,136 +0,0 @@
|
||||||
//! A language server scaffold, exposing a synchronous crossbeam-channel based API.
|
|
||||||
//! This crate handles protocol handshaking and parsing messages, while you
|
|
||||||
//! control the message dispatch loop yourself.
|
|
||||||
//!
|
|
||||||
//! Run with `RUST_LOG=gen_lsp_server=debug` to see all the messages.
|
|
||||||
//!
|
|
||||||
//! ```no_run
|
|
||||||
//! use std::error::Error;
|
|
||||||
//! use crossbeam_channel::{Sender, Receiver};
|
|
||||||
//! use lsp_types::{ServerCapabilities, InitializeParams, request::{GotoDefinition, GotoDefinitionResponse}};
|
|
||||||
//! use gen_lsp_server::{run_server, stdio_transport, handle_shutdown, RawMessage, RawResponse};
|
|
||||||
//!
|
|
||||||
//! fn main() -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
||||||
//! let (receiver, sender, io_threads) = stdio_transport();
|
|
||||||
//! run_server(
|
|
||||||
//! ServerCapabilities::default(),
|
|
||||||
//! receiver,
|
|
||||||
//! sender,
|
|
||||||
//! main_loop,
|
|
||||||
//! )?;
|
|
||||||
//! io_threads.join()?;
|
|
||||||
//! Ok(())
|
|
||||||
//! }
|
|
||||||
//!
|
|
||||||
//! fn main_loop(
|
|
||||||
//! _params: InitializeParams,
|
|
||||||
//! receiver: &Receiver<RawMessage>,
|
|
||||||
//! sender: &Sender<RawMessage>,
|
|
||||||
//! ) -> Result<(), Box<dyn Error + Send + Sync>> {
|
|
||||||
//! for msg in receiver {
|
|
||||||
//! match msg {
|
|
||||||
//! RawMessage::Request(req) => {
|
|
||||||
//! let req = match handle_shutdown(req, sender) {
|
|
||||||
//! None => return Ok(()),
|
|
||||||
//! Some(req) => req,
|
|
||||||
//! };
|
|
||||||
//! match req.cast::<GotoDefinition>() {
|
|
||||||
//! Ok((id, _params)) => {
|
|
||||||
//! let resp = RawResponse::ok::<GotoDefinition>(
|
|
||||||
//! id,
|
|
||||||
//! &Some(GotoDefinitionResponse::Array(Vec::new())),
|
|
||||||
//! );
|
|
||||||
//! sender.send(RawMessage::Response(resp))?;
|
|
||||||
//! continue;
|
|
||||||
//! },
|
|
||||||
//! Err(req) => req,
|
|
||||||
//! };
|
|
||||||
//! // ...
|
|
||||||
//! }
|
|
||||||
//! RawMessage::Response(_resp) => (),
|
|
||||||
//! RawMessage::Notification(_not) => (),
|
|
||||||
//! }
|
|
||||||
//! }
|
|
||||||
//! Ok(())
|
|
||||||
//! }
|
|
||||||
//! ```
|
|
||||||
|
|
||||||
use std::error::Error;
|
|
||||||
|
|
||||||
mod msg;
|
|
||||||
mod stdio;
|
|
||||||
|
|
||||||
use crossbeam_channel::{Receiver, Sender};
|
|
||||||
use lsp_types::{
|
|
||||||
notification::{Exit, Initialized},
|
|
||||||
request::{Initialize, Shutdown},
|
|
||||||
InitializeParams, InitializeResult, ServerCapabilities,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub type Result<T> = std::result::Result<T, Box<dyn Error + Send + Sync>>;
|
|
||||||
pub use crate::{
|
|
||||||
msg::{ErrorCode, RawMessage, RawNotification, RawRequest, RawResponse, RawResponseError},
|
|
||||||
stdio::{stdio_transport, Threads},
|
|
||||||
};
|
|
||||||
|
|
||||||
/// Main entry point: runs the server from initialization to shutdown.
|
|
||||||
/// To attach server to standard input/output streams, use the `stdio_transport`
|
|
||||||
/// function to create corresponding `sender` and `receiver` pair.
|
|
||||||
///
|
|
||||||
/// `server` should use the `handle_shutdown` function to handle the `Shutdown`
|
|
||||||
/// request.
|
|
||||||
pub fn run_server(
|
|
||||||
caps: ServerCapabilities,
|
|
||||||
receiver: Receiver<RawMessage>,
|
|
||||||
sender: Sender<RawMessage>,
|
|
||||||
server: impl FnOnce(InitializeParams, &Receiver<RawMessage>, &Sender<RawMessage>) -> Result<()>,
|
|
||||||
) -> Result<()> {
|
|
||||||
log::info!("lsp server initializes");
|
|
||||||
let params = initialize(&receiver, &sender, caps)?;
|
|
||||||
log::info!("lsp server initialized, serving requests");
|
|
||||||
server(params, &receiver, &sender)?;
|
|
||||||
log::info!("lsp server waiting for exit notification");
|
|
||||||
match receiver.recv() {
|
|
||||||
Ok(RawMessage::Notification(n)) => n
|
|
||||||
.cast::<Exit>()
|
|
||||||
.map_err(|n| format!("unexpected notification during shutdown: {:?}", n))?,
|
|
||||||
m => Err(format!("unexpected message during shutdown: {:?}", m))?,
|
|
||||||
}
|
|
||||||
log::info!("lsp server shutdown complete");
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// If `req` is `Shutdown`, respond to it and return `None`, otherwise return `Some(req)`
|
|
||||||
pub fn handle_shutdown(req: RawRequest, sender: &Sender<RawMessage>) -> Option<RawRequest> {
|
|
||||||
match req.cast::<Shutdown>() {
|
|
||||||
Ok((id, ())) => {
|
|
||||||
let resp = RawResponse::ok::<Shutdown>(id, &());
|
|
||||||
let _ = sender.send(RawMessage::Response(resp));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
Err(req) => Some(req),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn initialize(
|
|
||||||
receiver: &Receiver<RawMessage>,
|
|
||||||
sender: &Sender<RawMessage>,
|
|
||||||
caps: ServerCapabilities,
|
|
||||||
) -> Result<InitializeParams> {
|
|
||||||
let (id, params) = match receiver.recv() {
|
|
||||||
Ok(RawMessage::Request(req)) => match req.cast::<Initialize>() {
|
|
||||||
Err(req) => Err(format!("expected initialize request, got {:?}", req))?,
|
|
||||||
Ok(req) => req,
|
|
||||||
},
|
|
||||||
msg => Err(format!("expected initialize request, got {:?}", msg))?,
|
|
||||||
};
|
|
||||||
let resp = RawResponse::ok::<Initialize>(id, &InitializeResult { capabilities: caps });
|
|
||||||
sender.send(RawMessage::Response(resp)).unwrap();
|
|
||||||
match receiver.recv() {
|
|
||||||
Ok(RawMessage::Notification(n)) => {
|
|
||||||
n.cast::<Initialized>().map_err(|_| "expected initialized notification")?;
|
|
||||||
}
|
|
||||||
_ => Err("expected initialized notification".to_string())?,
|
|
||||||
}
|
|
||||||
Ok(params)
|
|
||||||
}
|
|
|
@ -1,205 +0,0 @@
|
||||||
use std::io::{BufRead, Write};
|
|
||||||
|
|
||||||
use lsp_types::{notification::Notification, request::Request};
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
use serde_json::{from_str, from_value, to_string, to_value, Value};
|
|
||||||
|
|
||||||
use crate::Result;
|
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone)]
|
|
||||||
#[serde(untagged)]
|
|
||||||
pub enum RawMessage {
|
|
||||||
Request(RawRequest),
|
|
||||||
Notification(RawNotification),
|
|
||||||
Response(RawResponse),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RawRequest> for RawMessage {
|
|
||||||
fn from(raw: RawRequest) -> RawMessage {
|
|
||||||
RawMessage::Request(raw)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RawNotification> for RawMessage {
|
|
||||||
fn from(raw: RawNotification) -> RawMessage {
|
|
||||||
RawMessage::Notification(raw)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<RawResponse> for RawMessage {
|
|
||||||
fn from(raw: RawResponse) -> RawMessage {
|
|
||||||
RawMessage::Response(raw)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct RawRequest {
|
|
||||||
pub id: u64,
|
|
||||||
pub method: String,
|
|
||||||
pub params: Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct RawResponse {
|
|
||||||
// JSON RPC allows this to be null if it was impossible
|
|
||||||
// to decode the request's id. Ignore this special case
|
|
||||||
// and just die horribly.
|
|
||||||
pub id: u64,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub result: Option<Value>,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub error: Option<RawResponseError>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct RawResponseError {
|
|
||||||
pub code: i32,
|
|
||||||
pub message: String,
|
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
|
||||||
pub data: Option<Value>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
#[allow(unused)]
|
|
||||||
pub enum ErrorCode {
|
|
||||||
ParseError = -32700,
|
|
||||||
InvalidRequest = -32600,
|
|
||||||
MethodNotFound = -32601,
|
|
||||||
InvalidParams = -32602,
|
|
||||||
InternalError = -32603,
|
|
||||||
ServerErrorStart = -32099,
|
|
||||||
ServerErrorEnd = -32000,
|
|
||||||
ServerNotInitialized = -32002,
|
|
||||||
UnknownErrorCode = -32001,
|
|
||||||
RequestCanceled = -32800,
|
|
||||||
ContentModified = -32801,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Serialize, Deserialize, Clone)]
|
|
||||||
pub struct RawNotification {
|
|
||||||
pub method: String,
|
|
||||||
pub params: Value,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawMessage {
|
|
||||||
pub fn read(r: &mut impl BufRead) -> Result<Option<RawMessage>> {
|
|
||||||
let text = match read_msg_text(r)? {
|
|
||||||
None => return Ok(None),
|
|
||||||
Some(text) => text,
|
|
||||||
};
|
|
||||||
let msg = from_str(&text)?;
|
|
||||||
Ok(Some(msg))
|
|
||||||
}
|
|
||||||
pub fn write(self, w: &mut impl Write) -> Result<()> {
|
|
||||||
#[derive(Serialize)]
|
|
||||||
struct JsonRpc {
|
|
||||||
jsonrpc: &'static str,
|
|
||||||
#[serde(flatten)]
|
|
||||||
msg: RawMessage,
|
|
||||||
}
|
|
||||||
let text = to_string(&JsonRpc { jsonrpc: "2.0", msg: self })?;
|
|
||||||
write_msg_text(w, &text)?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawRequest {
|
|
||||||
pub fn new<R>(id: u64, params: &R::Params) -> RawRequest
|
|
||||||
where
|
|
||||||
R: Request,
|
|
||||||
R::Params: serde::Serialize,
|
|
||||||
{
|
|
||||||
RawRequest { id, method: R::METHOD.to_string(), params: to_value(params).unwrap() }
|
|
||||||
}
|
|
||||||
pub fn cast<R>(self) -> ::std::result::Result<(u64, R::Params), RawRequest>
|
|
||||||
where
|
|
||||||
R: Request,
|
|
||||||
R::Params: serde::de::DeserializeOwned,
|
|
||||||
{
|
|
||||||
if self.method != R::METHOD {
|
|
||||||
return Err(self);
|
|
||||||
}
|
|
||||||
let id = self.id;
|
|
||||||
let params: R::Params = from_value(self.params).unwrap();
|
|
||||||
Ok((id, params))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawResponse {
|
|
||||||
pub fn ok<R>(id: u64, result: &R::Result) -> RawResponse
|
|
||||||
where
|
|
||||||
R: Request,
|
|
||||||
R::Result: serde::Serialize,
|
|
||||||
{
|
|
||||||
RawResponse { id, result: Some(to_value(&result).unwrap()), error: None }
|
|
||||||
}
|
|
||||||
pub fn err(id: u64, code: i32, message: String) -> RawResponse {
|
|
||||||
let error = RawResponseError { code, message, data: None };
|
|
||||||
RawResponse { id, result: None, error: Some(error) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl RawNotification {
|
|
||||||
pub fn new<N>(params: &N::Params) -> RawNotification
|
|
||||||
where
|
|
||||||
N: Notification,
|
|
||||||
N::Params: serde::Serialize,
|
|
||||||
{
|
|
||||||
RawNotification { method: N::METHOD.to_string(), params: to_value(params).unwrap() }
|
|
||||||
}
|
|
||||||
pub fn is<N>(&self) -> bool
|
|
||||||
where
|
|
||||||
N: Notification,
|
|
||||||
{
|
|
||||||
self.method == N::METHOD
|
|
||||||
}
|
|
||||||
pub fn cast<N>(self) -> ::std::result::Result<N::Params, RawNotification>
|
|
||||||
where
|
|
||||||
N: Notification,
|
|
||||||
N::Params: serde::de::DeserializeOwned,
|
|
||||||
{
|
|
||||||
if !self.is::<N>() {
|
|
||||||
return Err(self);
|
|
||||||
}
|
|
||||||
Ok(from_value(self.params).unwrap())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_msg_text(inp: &mut impl BufRead) -> Result<Option<String>> {
|
|
||||||
let mut size = None;
|
|
||||||
let mut buf = String::new();
|
|
||||||
loop {
|
|
||||||
buf.clear();
|
|
||||||
if inp.read_line(&mut buf)? == 0 {
|
|
||||||
return Ok(None);
|
|
||||||
}
|
|
||||||
if !buf.ends_with("\r\n") {
|
|
||||||
Err(format!("malformed header: {:?}", buf))?;
|
|
||||||
}
|
|
||||||
let buf = &buf[..buf.len() - 2];
|
|
||||||
if buf.is_empty() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
let mut parts = buf.splitn(2, ": ");
|
|
||||||
let header_name = parts.next().unwrap();
|
|
||||||
let header_value = parts.next().ok_or_else(|| format!("malformed header: {:?}", buf))?;
|
|
||||||
if header_name == "Content-Length" {
|
|
||||||
size = Some(header_value.parse::<usize>()?);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let size = size.ok_or("no Content-Length")?;
|
|
||||||
let mut buf = buf.into_bytes();
|
|
||||||
buf.resize(size, 0);
|
|
||||||
inp.read_exact(&mut buf)?;
|
|
||||||
let buf = String::from_utf8(buf)?;
|
|
||||||
log::debug!("< {}", buf);
|
|
||||||
Ok(Some(buf))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_msg_text(out: &mut impl Write, msg: &str) -> Result<()> {
|
|
||||||
log::debug!("> {}", msg);
|
|
||||||
write!(out, "Content-Length: {}\r\n\r\n", msg.len())?;
|
|
||||||
out.write_all(msg.as_bytes())?;
|
|
||||||
out.flush()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
|
@ -1,57 +0,0 @@
|
||||||
use std::{
|
|
||||||
io::{stdin, stdout},
|
|
||||||
thread,
|
|
||||||
};
|
|
||||||
|
|
||||||
use crossbeam_channel::{bounded, Receiver, Sender};
|
|
||||||
use lsp_types::notification::Exit;
|
|
||||||
|
|
||||||
use crate::{RawMessage, Result};
|
|
||||||
|
|
||||||
pub fn stdio_transport() -> (Receiver<RawMessage>, Sender<RawMessage>, Threads) {
|
|
||||||
let (writer_sender, writer_receiver) = bounded::<RawMessage>(16);
|
|
||||||
let writer = thread::spawn(move || {
|
|
||||||
let stdout = stdout();
|
|
||||||
let mut stdout = stdout.lock();
|
|
||||||
writer_receiver.into_iter().try_for_each(|it| it.write(&mut stdout))?;
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
let (reader_sender, reader_receiver) = bounded::<RawMessage>(16);
|
|
||||||
let reader = thread::spawn(move || {
|
|
||||||
let stdin = stdin();
|
|
||||||
let mut stdin = stdin.lock();
|
|
||||||
while let Some(msg) = RawMessage::read(&mut stdin)? {
|
|
||||||
let is_exit = match &msg {
|
|
||||||
RawMessage::Notification(n) => n.is::<Exit>(),
|
|
||||||
_ => false,
|
|
||||||
};
|
|
||||||
|
|
||||||
reader_sender.send(msg).unwrap();
|
|
||||||
|
|
||||||
if is_exit {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
});
|
|
||||||
let threads = Threads { reader, writer };
|
|
||||||
(reader_receiver, writer_sender, threads)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Threads {
|
|
||||||
reader: thread::JoinHandle<Result<()>>,
|
|
||||||
writer: thread::JoinHandle<Result<()>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Threads {
|
|
||||||
pub fn join(self) -> Result<()> {
|
|
||||||
match self.reader.join() {
|
|
||||||
Ok(r) => r?,
|
|
||||||
Err(_) => Err("reader panicked")?,
|
|
||||||
}
|
|
||||||
match self.writer.join() {
|
|
||||||
Ok(r) => r,
|
|
||||||
Err(_) => Err("writer panicked")?,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -21,7 +21,7 @@ thread_worker = { path = "../thread_worker" }
|
||||||
ra_syntax = { path = "../ra_syntax" }
|
ra_syntax = { path = "../ra_syntax" }
|
||||||
ra_text_edit = { path = "../ra_text_edit" }
|
ra_text_edit = { path = "../ra_text_edit" }
|
||||||
ra_ide_api = { path = "../ra_ide_api" }
|
ra_ide_api = { path = "../ra_ide_api" }
|
||||||
gen_lsp_server = { path = "../gen_lsp_server" }
|
lsp-server = "0.1.0"
|
||||||
ra_project_model = { path = "../ra_project_model" }
|
ra_project_model = { path = "../ra_project_model" }
|
||||||
ra_prof = { path = "../ra_prof" }
|
ra_prof = { path = "../ra_prof" }
|
||||||
ra_vfs_glob = { path = "../ra_vfs_glob" }
|
ra_vfs_glob = { path = "../ra_vfs_glob" }
|
||||||
|
|
|
@ -1,5 +1,5 @@
|
||||||
use flexi_logger::{Duplicate, Logger};
|
use flexi_logger::{Duplicate, Logger};
|
||||||
use gen_lsp_server::{run_server, stdio_transport};
|
use lsp_server::{run_server, stdio_transport, LspServerError};
|
||||||
|
|
||||||
use ra_lsp_server::{show_message, Result, ServerConfig};
|
use ra_lsp_server::{show_message, Result, ServerConfig};
|
||||||
use ra_prof;
|
use ra_prof;
|
||||||
|
@ -29,9 +29,11 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main_inner() -> Result<()> {
|
fn main_inner() -> Result<()> {
|
||||||
let (receiver, sender, threads) = stdio_transport();
|
let (sender, receiver, io_threads) = stdio_transport();
|
||||||
let cwd = std::env::current_dir()?;
|
let cwd = std::env::current_dir()?;
|
||||||
run_server(ra_lsp_server::server_capabilities(), receiver, sender, |params, r, s| {
|
let caps = serde_json::to_value(ra_lsp_server::server_capabilities()).unwrap();
|
||||||
|
run_server(caps, sender, receiver, |params, s, r| {
|
||||||
|
let params: lsp_types::InitializeParams = serde_json::from_value(params)?;
|
||||||
let root = params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
|
let root = params.root_uri.and_then(|it| it.to_file_path().ok()).unwrap_or(cwd);
|
||||||
|
|
||||||
let workspace_roots = params
|
let workspace_roots = params
|
||||||
|
@ -62,9 +64,13 @@ fn main_inner() -> Result<()> {
|
||||||
.unwrap_or_default();
|
.unwrap_or_default();
|
||||||
|
|
||||||
ra_lsp_server::main_loop(workspace_roots, params.capabilities, server_config, r, s)
|
ra_lsp_server::main_loop(workspace_roots, params.capabilities, server_config, r, s)
|
||||||
|
})
|
||||||
|
.map_err(|err| match err {
|
||||||
|
LspServerError::ProtocolError(err) => err.into(),
|
||||||
|
LspServerError::ServerError(err) => err,
|
||||||
})?;
|
})?;
|
||||||
log::info!("shutting down IO...");
|
log::info!("shutting down IO...");
|
||||||
threads.join()?;
|
io_threads.join()?;
|
||||||
log::info!("... IO is down");
|
log::info!("... IO is down");
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,7 @@ pub(crate) mod pending_requests;
|
||||||
use std::{error::Error, fmt, path::PathBuf, sync::Arc, time::Instant};
|
use std::{error::Error, fmt, path::PathBuf, sync::Arc, time::Instant};
|
||||||
|
|
||||||
use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender};
|
use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender};
|
||||||
use gen_lsp_server::{
|
use lsp_server::{handle_shutdown, ErrorCode, Message, Notification, Request, RequestId, Response};
|
||||||
handle_shutdown, ErrorCode, RawMessage, RawNotification, RawRequest, RawResponse,
|
|
||||||
};
|
|
||||||
use lsp_types::{ClientCapabilities, NumberOrString};
|
use lsp_types::{ClientCapabilities, NumberOrString};
|
||||||
use ra_ide_api::{Canceled, FeatureFlags, FileId, LibraryData};
|
use ra_ide_api::{Canceled, FeatureFlags, FileId, LibraryData};
|
||||||
use ra_prof::profile;
|
use ra_prof::profile;
|
||||||
|
@ -53,8 +51,8 @@ pub fn main_loop(
|
||||||
ws_roots: Vec<PathBuf>,
|
ws_roots: Vec<PathBuf>,
|
||||||
client_caps: ClientCapabilities,
|
client_caps: ClientCapabilities,
|
||||||
config: ServerConfig,
|
config: ServerConfig,
|
||||||
msg_receiver: &Receiver<RawMessage>,
|
msg_receiver: &Receiver<Message>,
|
||||||
msg_sender: &Sender<RawMessage>,
|
msg_sender: &Sender<Message>,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
log::info!("server_config: {:#?}", config);
|
log::info!("server_config: {:#?}", config);
|
||||||
// FIXME: support dynamic workspace loading.
|
// FIXME: support dynamic workspace loading.
|
||||||
|
@ -146,12 +144,12 @@ pub fn main_loop(
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
enum Task {
|
enum Task {
|
||||||
Respond(RawResponse),
|
Respond(Response),
|
||||||
Notify(RawNotification),
|
Notify(Notification),
|
||||||
}
|
}
|
||||||
|
|
||||||
enum Event {
|
enum Event {
|
||||||
Msg(RawMessage),
|
Msg(Message),
|
||||||
Task(Task),
|
Task(Task),
|
||||||
Vfs(VfsTask),
|
Vfs(VfsTask),
|
||||||
Lib(LibraryData),
|
Lib(LibraryData),
|
||||||
|
@ -159,24 +157,28 @@ enum Event {
|
||||||
|
|
||||||
impl fmt::Debug for Event {
|
impl fmt::Debug for Event {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
let debug_verbose_not = |not: &RawNotification, f: &mut fmt::Formatter| {
|
let debug_verbose_not = |not: &Notification, f: &mut fmt::Formatter| {
|
||||||
f.debug_struct("RawNotification").field("method", ¬.method).finish()
|
f.debug_struct("Notification").field("method", ¬.method).finish()
|
||||||
};
|
};
|
||||||
|
|
||||||
match self {
|
match self {
|
||||||
Event::Msg(RawMessage::Notification(not)) => {
|
Event::Msg(Message::Notification(not)) => {
|
||||||
if not.is::<req::DidOpenTextDocument>() || not.is::<req::DidChangeTextDocument>() {
|
if notification_is::<req::DidOpenTextDocument>(not)
|
||||||
|
|| notification_is::<req::DidChangeTextDocument>(not)
|
||||||
|
{
|
||||||
return debug_verbose_not(not, f);
|
return debug_verbose_not(not, f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Task(Task::Notify(not)) => {
|
Event::Task(Task::Notify(not)) => {
|
||||||
if not.is::<req::PublishDecorations>() || not.is::<req::PublishDiagnostics>() {
|
if notification_is::<req::PublishDecorations>(not)
|
||||||
|
|| notification_is::<req::PublishDiagnostics>(not)
|
||||||
|
{
|
||||||
return debug_verbose_not(not, f);
|
return debug_verbose_not(not, f);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Event::Task(Task::Respond(resp)) => {
|
Event::Task(Task::Respond(resp)) => {
|
||||||
return f
|
return f
|
||||||
.debug_struct("RawResponse")
|
.debug_struct("Response")
|
||||||
.field("id", &resp.id)
|
.field("id", &resp.id)
|
||||||
.field("error", &resp.error)
|
.field("error", &resp.error)
|
||||||
.finish();
|
.finish();
|
||||||
|
@ -194,8 +196,8 @@ impl fmt::Debug for Event {
|
||||||
|
|
||||||
fn main_loop_inner(
|
fn main_loop_inner(
|
||||||
pool: &ThreadPool,
|
pool: &ThreadPool,
|
||||||
msg_sender: &Sender<RawMessage>,
|
msg_sender: &Sender<Message>,
|
||||||
msg_receiver: &Receiver<RawMessage>,
|
msg_receiver: &Receiver<Message>,
|
||||||
task_sender: Sender<Task>,
|
task_sender: Sender<Task>,
|
||||||
task_receiver: Receiver<Task>,
|
task_receiver: Receiver<Task>,
|
||||||
state: &mut WorldState,
|
state: &mut WorldState,
|
||||||
|
@ -249,10 +251,9 @@ fn main_loop_inner(
|
||||||
in_flight_libraries -= 1;
|
in_flight_libraries -= 1;
|
||||||
}
|
}
|
||||||
Event::Msg(msg) => match msg {
|
Event::Msg(msg) => match msg {
|
||||||
RawMessage::Request(req) => {
|
Message::Request(req) => {
|
||||||
let req = match handle_shutdown(req, msg_sender) {
|
if handle_shutdown(&req, msg_sender) {
|
||||||
Some(req) => req,
|
return Ok(());
|
||||||
None => return Ok(()),
|
|
||||||
};
|
};
|
||||||
on_request(
|
on_request(
|
||||||
state,
|
state,
|
||||||
|
@ -264,11 +265,11 @@ fn main_loop_inner(
|
||||||
req,
|
req,
|
||||||
)?
|
)?
|
||||||
}
|
}
|
||||||
RawMessage::Notification(not) => {
|
Message::Notification(not) => {
|
||||||
on_notification(msg_sender, state, pending_requests, &mut subs, not)?;
|
on_notification(msg_sender, state, pending_requests, &mut subs, not)?;
|
||||||
state_changed = true;
|
state_changed = true;
|
||||||
}
|
}
|
||||||
RawMessage::Response(resp) => log::error!("unexpected response: {:?}", resp),
|
Message::Response(resp) => log::error!("unexpected response: {:?}", resp),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -313,13 +314,13 @@ fn main_loop_inner(
|
||||||
|
|
||||||
fn on_task(
|
fn on_task(
|
||||||
task: Task,
|
task: Task,
|
||||||
msg_sender: &Sender<RawMessage>,
|
msg_sender: &Sender<Message>,
|
||||||
pending_requests: &mut PendingRequests,
|
pending_requests: &mut PendingRequests,
|
||||||
state: &mut WorldState,
|
state: &mut WorldState,
|
||||||
) {
|
) {
|
||||||
match task {
|
match task {
|
||||||
Task::Respond(response) => {
|
Task::Respond(response) => {
|
||||||
if let Some(completed) = pending_requests.finish(response.id) {
|
if let Some(completed) = pending_requests.finish(&response.id) {
|
||||||
log::info!("handled req#{} in {:?}", completed.id, completed.duration);
|
log::info!("handled req#{} in {:?}", completed.id, completed.duration);
|
||||||
state.complete_request(completed);
|
state.complete_request(completed);
|
||||||
msg_sender.send(response.into()).unwrap();
|
msg_sender.send(response.into()).unwrap();
|
||||||
|
@ -336,9 +337,9 @@ fn on_request(
|
||||||
pending_requests: &mut PendingRequests,
|
pending_requests: &mut PendingRequests,
|
||||||
pool: &ThreadPool,
|
pool: &ThreadPool,
|
||||||
sender: &Sender<Task>,
|
sender: &Sender<Task>,
|
||||||
msg_sender: &Sender<RawMessage>,
|
msg_sender: &Sender<Message>,
|
||||||
request_received: Instant,
|
request_received: Instant,
|
||||||
req: RawRequest,
|
req: Request,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let mut pool_dispatcher = PoolDispatcher {
|
let mut pool_dispatcher = PoolDispatcher {
|
||||||
req: Some(req),
|
req: Some(req),
|
||||||
|
@ -388,22 +389,20 @@ fn on_request(
|
||||||
}
|
}
|
||||||
|
|
||||||
fn on_notification(
|
fn on_notification(
|
||||||
msg_sender: &Sender<RawMessage>,
|
msg_sender: &Sender<Message>,
|
||||||
state: &mut WorldState,
|
state: &mut WorldState,
|
||||||
pending_requests: &mut PendingRequests,
|
pending_requests: &mut PendingRequests,
|
||||||
subs: &mut Subscriptions,
|
subs: &mut Subscriptions,
|
||||||
not: RawNotification,
|
not: Notification,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let not = match not.cast::<req::Cancel>() {
|
let not = match notification_cast::<req::Cancel>(not) {
|
||||||
Ok(params) => {
|
Ok(params) => {
|
||||||
let id = match params.id {
|
let id: RequestId = match params.id {
|
||||||
NumberOrString::Number(id) => id,
|
NumberOrString::Number(id) => id.into(),
|
||||||
NumberOrString::String(id) => {
|
NumberOrString::String(id) => id.into(),
|
||||||
panic!("string id's not supported: {:?}", id);
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
if pending_requests.cancel(id) {
|
if pending_requests.cancel(&id) {
|
||||||
let response = RawResponse::err(
|
let response = Response::new_err(
|
||||||
id,
|
id,
|
||||||
ErrorCode::RequestCanceled as i32,
|
ErrorCode::RequestCanceled as i32,
|
||||||
"canceled by client".to_string(),
|
"canceled by client".to_string(),
|
||||||
|
@ -414,7 +413,7 @@ fn on_notification(
|
||||||
}
|
}
|
||||||
Err(not) => not,
|
Err(not) => not,
|
||||||
};
|
};
|
||||||
let not = match not.cast::<req::DidOpenTextDocument>() {
|
let not = match notification_cast::<req::DidOpenTextDocument>(not) {
|
||||||
Ok(params) => {
|
Ok(params) => {
|
||||||
let uri = params.text_document.uri;
|
let uri = params.text_document.uri;
|
||||||
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
||||||
|
@ -427,7 +426,7 @@ fn on_notification(
|
||||||
}
|
}
|
||||||
Err(not) => not,
|
Err(not) => not,
|
||||||
};
|
};
|
||||||
let not = match not.cast::<req::DidChangeTextDocument>() {
|
let not = match notification_cast::<req::DidChangeTextDocument>(not) {
|
||||||
Ok(mut params) => {
|
Ok(mut params) => {
|
||||||
let uri = params.text_document.uri;
|
let uri = params.text_document.uri;
|
||||||
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
||||||
|
@ -438,7 +437,7 @@ fn on_notification(
|
||||||
}
|
}
|
||||||
Err(not) => not,
|
Err(not) => not,
|
||||||
};
|
};
|
||||||
let not = match not.cast::<req::DidCloseTextDocument>() {
|
let not = match notification_cast::<req::DidCloseTextDocument>(not) {
|
||||||
Ok(params) => {
|
Ok(params) => {
|
||||||
let uri = params.text_document.uri;
|
let uri = params.text_document.uri;
|
||||||
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
||||||
|
@ -446,13 +445,13 @@ fn on_notification(
|
||||||
subs.remove_sub(FileId(file_id.0));
|
subs.remove_sub(FileId(file_id.0));
|
||||||
}
|
}
|
||||||
let params = req::PublishDiagnosticsParams { uri, diagnostics: Vec::new() };
|
let params = req::PublishDiagnosticsParams { uri, diagnostics: Vec::new() };
|
||||||
let not = RawNotification::new::<req::PublishDiagnostics>(¶ms);
|
let not = notification_new::<req::PublishDiagnostics>(params);
|
||||||
msg_sender.send(not.into()).unwrap();
|
msg_sender.send(not.into()).unwrap();
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
Err(not) => not,
|
Err(not) => not,
|
||||||
};
|
};
|
||||||
let not = match not.cast::<req::DidChangeConfiguration>() {
|
let not = match notification_cast::<req::DidChangeConfiguration>(not) {
|
||||||
Ok(_params) => {
|
Ok(_params) => {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
@ -463,11 +462,11 @@ fn on_notification(
|
||||||
}
|
}
|
||||||
|
|
||||||
struct PoolDispatcher<'a> {
|
struct PoolDispatcher<'a> {
|
||||||
req: Option<RawRequest>,
|
req: Option<Request>,
|
||||||
pool: &'a ThreadPool,
|
pool: &'a ThreadPool,
|
||||||
world: &'a mut WorldState,
|
world: &'a mut WorldState,
|
||||||
pending_requests: &'a mut PendingRequests,
|
pending_requests: &'a mut PendingRequests,
|
||||||
msg_sender: &'a Sender<RawMessage>,
|
msg_sender: &'a Sender<Message>,
|
||||||
sender: &'a Sender<Task>,
|
sender: &'a Sender<Task>,
|
||||||
request_received: Instant,
|
request_received: Instant,
|
||||||
}
|
}
|
||||||
|
@ -522,13 +521,13 @@ impl<'a> PoolDispatcher<'a> {
|
||||||
Ok(self)
|
Ok(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse<R>(&mut self) -> Option<(u64, R::Params)>
|
fn parse<R>(&mut self) -> Option<(RequestId, R::Params)>
|
||||||
where
|
where
|
||||||
R: req::Request + 'static,
|
R: req::Request + 'static,
|
||||||
R::Params: DeserializeOwned + Send + 'static,
|
R::Params: DeserializeOwned + Send + 'static,
|
||||||
{
|
{
|
||||||
let req = self.req.take()?;
|
let req = self.req.take()?;
|
||||||
let (id, params) = match req.cast::<R>() {
|
let (id, params) = match req.extract::<R::Params>(R::METHOD) {
|
||||||
Ok(it) => it,
|
Ok(it) => it,
|
||||||
Err(req) => {
|
Err(req) => {
|
||||||
self.req = Some(req);
|
self.req = Some(req);
|
||||||
|
@ -536,7 +535,7 @@ impl<'a> PoolDispatcher<'a> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
self.pending_requests.start(PendingRequest {
|
self.pending_requests.start(PendingRequest {
|
||||||
id,
|
id: id.clone(),
|
||||||
method: R::METHOD.to_string(),
|
method: R::METHOD.to_string(),
|
||||||
received: self.request_received,
|
received: self.request_received,
|
||||||
});
|
});
|
||||||
|
@ -548,7 +547,7 @@ impl<'a> PoolDispatcher<'a> {
|
||||||
None => (),
|
None => (),
|
||||||
Some(req) => {
|
Some(req) => {
|
||||||
log::error!("unknown request: {:?}", req);
|
log::error!("unknown request: {:?}", req);
|
||||||
let resp = RawResponse::err(
|
let resp = Response::new_err(
|
||||||
req.id,
|
req.id,
|
||||||
ErrorCode::MethodNotFound as i32,
|
ErrorCode::MethodNotFound as i32,
|
||||||
"unknown request".to_string(),
|
"unknown request".to_string(),
|
||||||
|
@ -559,34 +558,30 @@ impl<'a> PoolDispatcher<'a> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn result_to_task<R>(id: u64, result: Result<R::Result>) -> Task
|
fn result_to_task<R>(id: RequestId, result: Result<R::Result>) -> Task
|
||||||
where
|
where
|
||||||
R: req::Request + 'static,
|
R: req::Request + 'static,
|
||||||
R::Params: DeserializeOwned + Send + 'static,
|
R::Params: DeserializeOwned + Send + 'static,
|
||||||
R::Result: Serialize + 'static,
|
R::Result: Serialize + 'static,
|
||||||
{
|
{
|
||||||
let response = match result {
|
let response = match result {
|
||||||
Ok(resp) => RawResponse::ok::<R>(id, &resp),
|
Ok(resp) => Response::new_ok(id, &resp),
|
||||||
Err(e) => match e.downcast::<LspError>() {
|
Err(e) => match e.downcast::<LspError>() {
|
||||||
Ok(lsp_error) => RawResponse::err(id, lsp_error.code, lsp_error.message),
|
Ok(lsp_error) => Response::new_err(id, lsp_error.code, lsp_error.message),
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
if is_canceled(&e) {
|
if is_canceled(&e) {
|
||||||
// FIXME: When https://github.com/Microsoft/vscode-languageserver-node/issues/457
|
// FIXME: When https://github.com/Microsoft/vscode-languageserver-node/issues/457
|
||||||
// gets fixed, we can return the proper response.
|
// gets fixed, we can return the proper response.
|
||||||
// This works around the issue where "content modified" error would continuously
|
// This works around the issue where "content modified" error would continuously
|
||||||
// show an message pop-up in VsCode
|
// show an message pop-up in VsCode
|
||||||
// RawResponse::err(
|
// Response::err(
|
||||||
// id,
|
// id,
|
||||||
// ErrorCode::ContentModified as i32,
|
// ErrorCode::ContentModified as i32,
|
||||||
// "content modified".to_string(),
|
// "content modified".to_string(),
|
||||||
// )
|
// )
|
||||||
RawResponse {
|
Response::new_ok(id, ())
|
||||||
id,
|
|
||||||
result: Some(serde_json::to_value(&()).unwrap()),
|
|
||||||
error: None,
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
RawResponse::err(id, ErrorCode::InternalError as i32, e.to_string())
|
Response::new_err(id, ErrorCode::InternalError as i32, e.to_string())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -613,7 +608,7 @@ fn update_file_notifications_on_threadpool(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(params) => {
|
Ok(params) => {
|
||||||
let not = RawNotification::new::<req::PublishDiagnostics>(¶ms);
|
let not = notification_new::<req::PublishDiagnostics>(params);
|
||||||
sender.send(Task::Notify(not)).unwrap();
|
sender.send(Task::Notify(not)).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -626,7 +621,7 @@ fn update_file_notifications_on_threadpool(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(params) => {
|
Ok(params) => {
|
||||||
let not = RawNotification::new::<req::PublishDecorations>(¶ms);
|
let not = notification_new::<req::PublishDecorations>(params);
|
||||||
sender.send(Task::Notify(not)).unwrap();
|
sender.send(Task::Notify(not)).unwrap();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -635,17 +630,33 @@ fn update_file_notifications_on_threadpool(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn show_message(
|
pub fn show_message(typ: req::MessageType, message: impl Into<String>, sender: &Sender<Message>) {
|
||||||
typ: req::MessageType,
|
|
||||||
message: impl Into<String>,
|
|
||||||
sender: &Sender<RawMessage>,
|
|
||||||
) {
|
|
||||||
let message = message.into();
|
let message = message.into();
|
||||||
let params = req::ShowMessageParams { typ, message };
|
let params = req::ShowMessageParams { typ, message };
|
||||||
let not = RawNotification::new::<req::ShowMessage>(¶ms);
|
let not = notification_new::<req::ShowMessage>(params);
|
||||||
sender.send(not.into()).unwrap();
|
sender.send(not.into()).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_canceled(e: &Box<dyn std::error::Error + Send + Sync>) -> bool {
|
fn is_canceled(e: &Box<dyn std::error::Error + Send + Sync>) -> bool {
|
||||||
e.downcast_ref::<Canceled>().is_some()
|
e.downcast_ref::<Canceled>().is_some()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn notification_is<N: lsp_types::notification::Notification>(notification: &Notification) -> bool {
|
||||||
|
notification.method == N::METHOD
|
||||||
|
}
|
||||||
|
|
||||||
|
fn notification_cast<N>(notification: Notification) -> std::result::Result<N::Params, Notification>
|
||||||
|
where
|
||||||
|
N: lsp_types::notification::Notification,
|
||||||
|
N::Params: DeserializeOwned,
|
||||||
|
{
|
||||||
|
notification.extract(N::METHOD)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn notification_new<N>(params: N::Params) -> Notification
|
||||||
|
where
|
||||||
|
N: lsp_types::notification::Notification,
|
||||||
|
N::Params: Serialize,
|
||||||
|
{
|
||||||
|
Notification::new(N::METHOD.to_string(), params)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
use std::{fmt::Write as _, io::Write as _};
|
use std::{fmt::Write as _, io::Write as _};
|
||||||
|
|
||||||
use gen_lsp_server::ErrorCode;
|
use lsp_server::ErrorCode;
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
|
CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
|
||||||
DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeKind,
|
DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeKind,
|
||||||
|
|
|
@ -1,17 +1,18 @@
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use lsp_server::RequestId;
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct CompletedRequest {
|
pub struct CompletedRequest {
|
||||||
pub id: u64,
|
pub id: RequestId,
|
||||||
pub method: String,
|
pub method: String,
|
||||||
pub duration: Duration,
|
pub duration: Duration,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct PendingRequest {
|
pub(crate) struct PendingRequest {
|
||||||
pub(crate) id: u64,
|
pub(crate) id: RequestId,
|
||||||
pub(crate) method: String,
|
pub(crate) method: String,
|
||||||
pub(crate) received: Instant,
|
pub(crate) received: Instant,
|
||||||
}
|
}
|
||||||
|
@ -28,20 +29,20 @@ impl From<PendingRequest> for CompletedRequest {
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub(crate) struct PendingRequests {
|
pub(crate) struct PendingRequests {
|
||||||
map: FxHashMap<u64, PendingRequest>,
|
map: FxHashMap<RequestId, PendingRequest>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PendingRequests {
|
impl PendingRequests {
|
||||||
pub(crate) fn start(&mut self, request: PendingRequest) {
|
pub(crate) fn start(&mut self, request: PendingRequest) {
|
||||||
let id = request.id;
|
let id = request.id.clone();
|
||||||
let prev = self.map.insert(id, request);
|
let prev = self.map.insert(id.clone(), request);
|
||||||
assert!(prev.is_none(), "duplicate request with id {}", id);
|
assert!(prev.is_none(), "duplicate request with id {}", id);
|
||||||
}
|
}
|
||||||
pub(crate) fn cancel(&mut self, id: u64) -> bool {
|
pub(crate) fn cancel(&mut self, id: &RequestId) -> bool {
|
||||||
self.map.remove(&id).is_some()
|
self.map.remove(id).is_some()
|
||||||
}
|
}
|
||||||
pub(crate) fn finish(&mut self, id: u64) -> Option<CompletedRequest> {
|
pub(crate) fn finish(&mut self, id: &RequestId) -> Option<CompletedRequest> {
|
||||||
self.map.remove(&id).map(CompletedRequest::from)
|
self.map.remove(id).map(CompletedRequest::from)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use crossbeam_channel::{unbounded, Receiver};
|
use crossbeam_channel::{unbounded, Receiver};
|
||||||
use gen_lsp_server::ErrorCode;
|
use lsp_server::ErrorCode;
|
||||||
use lsp_types::Url;
|
use lsp_types::Url;
|
||||||
use parking_lot::RwLock;
|
use parking_lot::RwLock;
|
||||||
use ra_ide_api::{
|
use ra_ide_api::{
|
||||||
|
|
|
@ -8,13 +8,10 @@ use std::{
|
||||||
|
|
||||||
use crossbeam_channel::{after, select, Receiver};
|
use crossbeam_channel::{after, select, Receiver};
|
||||||
use flexi_logger::Logger;
|
use flexi_logger::Logger;
|
||||||
use gen_lsp_server::{RawMessage, RawNotification, RawRequest};
|
use lsp_server::{Message, Notification, Request};
|
||||||
use lsp_types::{
|
use lsp_types::{
|
||||||
notification::DidOpenTextDocument,
|
request::Shutdown, ClientCapabilities, DidOpenTextDocumentParams, GotoCapability,
|
||||||
notification::{Notification, ShowMessage},
|
TextDocumentClientCapabilities, TextDocumentIdentifier, TextDocumentItem, Url,
|
||||||
request::{Request, Shutdown},
|
|
||||||
ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities,
|
|
||||||
TextDocumentIdentifier, TextDocumentItem, Url,
|
|
||||||
};
|
};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use serde_json::{to_string_pretty, Value};
|
use serde_json::{to_string_pretty, Value};
|
||||||
|
@ -84,9 +81,9 @@ pub fn project(fixture: &str) -> Server {
|
||||||
|
|
||||||
pub struct Server {
|
pub struct Server {
|
||||||
req_id: Cell<u64>,
|
req_id: Cell<u64>,
|
||||||
messages: RefCell<Vec<RawMessage>>,
|
messages: RefCell<Vec<Message>>,
|
||||||
dir: TempDir,
|
dir: TempDir,
|
||||||
worker: Worker<RawMessage, RawMessage>,
|
worker: Worker<Message, Message>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Server {
|
impl Server {
|
||||||
|
@ -100,7 +97,7 @@ impl Server {
|
||||||
|
|
||||||
let roots = if roots.is_empty() { vec![path] } else { roots };
|
let roots = if roots.is_empty() { vec![path] } else { roots };
|
||||||
|
|
||||||
let worker = Worker::<RawMessage, RawMessage>::spawn(
|
let worker = Worker::<Message, Message>::spawn(
|
||||||
"test server",
|
"test server",
|
||||||
128,
|
128,
|
||||||
move |msg_receiver, msg_sender| {
|
move |msg_receiver, msg_sender| {
|
||||||
|
@ -128,7 +125,8 @@ impl Server {
|
||||||
let res = Server { req_id: Cell::new(1), dir, messages: Default::default(), worker };
|
let res = Server { req_id: Cell::new(1), dir, messages: Default::default(), worker };
|
||||||
|
|
||||||
for (path, text) in files {
|
for (path, text) in files {
|
||||||
res.send_notification(RawNotification::new::<DidOpenTextDocument>(
|
res.send_notification(Notification::new(
|
||||||
|
"textDocument/didOpen".to_string(),
|
||||||
&DidOpenTextDocumentParams {
|
&DidOpenTextDocumentParams {
|
||||||
text_document: TextDocumentItem {
|
text_document: TextDocumentItem {
|
||||||
uri: Url::from_file_path(path).unwrap(),
|
uri: Url::from_file_path(path).unwrap(),
|
||||||
|
@ -149,16 +147,16 @@ impl Server {
|
||||||
|
|
||||||
pub fn notification<N>(&self, params: N::Params)
|
pub fn notification<N>(&self, params: N::Params)
|
||||||
where
|
where
|
||||||
N: Notification,
|
N: lsp_types::notification::Notification,
|
||||||
N::Params: Serialize,
|
N::Params: Serialize,
|
||||||
{
|
{
|
||||||
let r = RawNotification::new::<N>(¶ms);
|
let r = Notification::new(N::METHOD.to_string(), params);
|
||||||
self.send_notification(r)
|
self.send_notification(r)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn request<R>(&self, params: R::Params, expected_resp: Value)
|
pub fn request<R>(&self, params: R::Params, expected_resp: Value)
|
||||||
where
|
where
|
||||||
R: Request,
|
R: lsp_types::request::Request,
|
||||||
R::Params: Serialize,
|
R::Params: Serialize,
|
||||||
{
|
{
|
||||||
let actual = self.send_request::<R>(params);
|
let actual = self.send_request::<R>(params);
|
||||||
|
@ -175,23 +173,23 @@ impl Server {
|
||||||
|
|
||||||
pub fn send_request<R>(&self, params: R::Params) -> Value
|
pub fn send_request<R>(&self, params: R::Params) -> Value
|
||||||
where
|
where
|
||||||
R: Request,
|
R: lsp_types::request::Request,
|
||||||
R::Params: Serialize,
|
R::Params: Serialize,
|
||||||
{
|
{
|
||||||
let id = self.req_id.get();
|
let id = self.req_id.get();
|
||||||
self.req_id.set(id + 1);
|
self.req_id.set(id + 1);
|
||||||
|
|
||||||
let r = RawRequest::new::<R>(id, ¶ms);
|
let r = Request::new(id.into(), R::METHOD.to_string(), params);
|
||||||
self.send_request_(r)
|
self.send_request_(r)
|
||||||
}
|
}
|
||||||
fn send_request_(&self, r: RawRequest) -> Value {
|
fn send_request_(&self, r: Request) -> Value {
|
||||||
let id = r.id;
|
let id = r.id.clone();
|
||||||
self.worker.sender().send(RawMessage::Request(r)).unwrap();
|
self.worker.sender().send(r.into()).unwrap();
|
||||||
while let Some(msg) = self.recv() {
|
while let Some(msg) = self.recv() {
|
||||||
match msg {
|
match msg {
|
||||||
RawMessage::Request(req) => panic!("unexpected request: {:?}", req),
|
Message::Request(req) => panic!("unexpected request: {:?}", req),
|
||||||
RawMessage::Notification(_) => (),
|
Message::Notification(_) => (),
|
||||||
RawMessage::Response(res) => {
|
Message::Response(res) => {
|
||||||
assert_eq!(res.id, id);
|
assert_eq!(res.id, id);
|
||||||
if let Some(err) = res.error {
|
if let Some(err) = res.error {
|
||||||
panic!("error response: {:#?}", err);
|
panic!("error response: {:#?}", err);
|
||||||
|
@ -203,15 +201,16 @@ impl Server {
|
||||||
panic!("no response");
|
panic!("no response");
|
||||||
}
|
}
|
||||||
pub fn wait_until_workspace_is_loaded(&self) {
|
pub fn wait_until_workspace_is_loaded(&self) {
|
||||||
self.wait_for_message_cond(1, &|msg: &RawMessage| match msg {
|
self.wait_for_message_cond(1, &|msg: &Message| match msg {
|
||||||
RawMessage::Notification(n) if n.method == ShowMessage::METHOD => {
|
Message::Notification(n) if n.method == "window/showMessage" => {
|
||||||
let msg = n.clone().cast::<req::ShowMessage>().unwrap();
|
let msg =
|
||||||
|
n.clone().extract::<req::ShowMessageParams>("window/showMessage").unwrap();
|
||||||
msg.message.starts_with("workspace loaded")
|
msg.message.starts_with("workspace loaded")
|
||||||
}
|
}
|
||||||
_ => false,
|
_ => false,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn wait_for_message_cond(&self, n: usize, cond: &dyn Fn(&RawMessage) -> bool) {
|
fn wait_for_message_cond(&self, n: usize, cond: &dyn Fn(&Message) -> bool) {
|
||||||
let mut total = 0;
|
let mut total = 0;
|
||||||
for msg in self.messages.borrow().iter() {
|
for msg in self.messages.borrow().iter() {
|
||||||
if cond(msg) {
|
if cond(msg) {
|
||||||
|
@ -225,14 +224,14 @@ impl Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn recv(&self) -> Option<RawMessage> {
|
fn recv(&self) -> Option<Message> {
|
||||||
recv_timeout(&self.worker.receiver()).map(|msg| {
|
recv_timeout(&self.worker.receiver()).map(|msg| {
|
||||||
self.messages.borrow_mut().push(msg.clone());
|
self.messages.borrow_mut().push(msg.clone());
|
||||||
msg
|
msg
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
fn send_notification(&self, not: RawNotification) {
|
fn send_notification(&self, not: Notification) {
|
||||||
self.worker.sender().send(RawMessage::Notification(not)).unwrap();
|
self.worker.sender().send(Message::Notification(not)).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn path(&self) -> &Path {
|
pub fn path(&self) -> &Path {
|
||||||
|
@ -246,7 +245,7 @@ impl Drop for Server {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn recv_timeout(receiver: &Receiver<RawMessage>) -> Option<RawMessage> {
|
fn recv_timeout(receiver: &Receiver<Message>) -> Option<Message> {
|
||||||
let timeout = Duration::from_secs(120);
|
let timeout = Duration::from_secs(120);
|
||||||
select! {
|
select! {
|
||||||
recv(receiver) -> msg => msg.ok(),
|
recv(receiver) -> msg => msg.ok(),
|
||||||
|
|
Loading…
Reference in a new issue