mirror of
https://github.com/rust-lang/rust-analyzer
synced 2024-11-10 07:04:22 +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)",
|
||||
]
|
||||
|
||||
[[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]]
|
||||
name = "console"
|
||||
version = "0.8.0"
|
||||
|
@ -472,18 +455,6 @@ name = "fuchsia-zircon-sys"
|
|||
version = "0.3.3"
|
||||
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]]
|
||||
name = "getrandom"
|
||||
version = "0.1.10"
|
||||
|
@ -539,7 +510,7 @@ name = "indicatif"
|
|||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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)",
|
||||
"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)",
|
||||
|
@ -689,6 +660,17 @@ dependencies = [
|
|||
"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]]
|
||||
name = "lsp-types"
|
||||
version = "0.60.0"
|
||||
|
@ -1092,8 +1074,8 @@ version = "0.1.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)",
|
||||
"gen_lsp_server 0.2.0",
|
||||
"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)",
|
||||
"parking_lot 0.9.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"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 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 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 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"
|
||||
|
@ -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 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 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 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"
|
||||
|
|
|
@ -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_text_edit = { path = "../ra_text_edit" }
|
||||
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_prof = { path = "../ra_prof" }
|
||||
ra_vfs_glob = { path = "../ra_vfs_glob" }
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
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_prof;
|
||||
|
@ -29,9 +29,11 @@ fn main() -> Result<()> {
|
|||
}
|
||||
|
||||
fn main_inner() -> Result<()> {
|
||||
let (receiver, sender, threads) = stdio_transport();
|
||||
let (sender, receiver, io_threads) = stdio_transport();
|
||||
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 workspace_roots = params
|
||||
|
@ -62,9 +64,13 @@ fn main_inner() -> Result<()> {
|
|||
.unwrap_or_default();
|
||||
|
||||
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...");
|
||||
threads.join()?;
|
||||
io_threads.join()?;
|
||||
log::info!("... IO is down");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -5,9 +5,7 @@ pub(crate) mod pending_requests;
|
|||
use std::{error::Error, fmt, path::PathBuf, sync::Arc, time::Instant};
|
||||
|
||||
use crossbeam_channel::{select, unbounded, Receiver, RecvError, Sender};
|
||||
use gen_lsp_server::{
|
||||
handle_shutdown, ErrorCode, RawMessage, RawNotification, RawRequest, RawResponse,
|
||||
};
|
||||
use lsp_server::{handle_shutdown, ErrorCode, Message, Notification, Request, RequestId, Response};
|
||||
use lsp_types::{ClientCapabilities, NumberOrString};
|
||||
use ra_ide_api::{Canceled, FeatureFlags, FileId, LibraryData};
|
||||
use ra_prof::profile;
|
||||
|
@ -53,8 +51,8 @@ pub fn main_loop(
|
|||
ws_roots: Vec<PathBuf>,
|
||||
client_caps: ClientCapabilities,
|
||||
config: ServerConfig,
|
||||
msg_receiver: &Receiver<RawMessage>,
|
||||
msg_sender: &Sender<RawMessage>,
|
||||
msg_receiver: &Receiver<Message>,
|
||||
msg_sender: &Sender<Message>,
|
||||
) -> Result<()> {
|
||||
log::info!("server_config: {:#?}", config);
|
||||
// FIXME: support dynamic workspace loading.
|
||||
|
@ -146,12 +144,12 @@ pub fn main_loop(
|
|||
|
||||
#[derive(Debug)]
|
||||
enum Task {
|
||||
Respond(RawResponse),
|
||||
Notify(RawNotification),
|
||||
Respond(Response),
|
||||
Notify(Notification),
|
||||
}
|
||||
|
||||
enum Event {
|
||||
Msg(RawMessage),
|
||||
Msg(Message),
|
||||
Task(Task),
|
||||
Vfs(VfsTask),
|
||||
Lib(LibraryData),
|
||||
|
@ -159,24 +157,28 @@ enum Event {
|
|||
|
||||
impl fmt::Debug for Event {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let debug_verbose_not = |not: &RawNotification, f: &mut fmt::Formatter| {
|
||||
f.debug_struct("RawNotification").field("method", ¬.method).finish()
|
||||
let debug_verbose_not = |not: &Notification, f: &mut fmt::Formatter| {
|
||||
f.debug_struct("Notification").field("method", ¬.method).finish()
|
||||
};
|
||||
|
||||
match self {
|
||||
Event::Msg(RawMessage::Notification(not)) => {
|
||||
if not.is::<req::DidOpenTextDocument>() || not.is::<req::DidChangeTextDocument>() {
|
||||
Event::Msg(Message::Notification(not)) => {
|
||||
if notification_is::<req::DidOpenTextDocument>(not)
|
||||
|| notification_is::<req::DidChangeTextDocument>(not)
|
||||
{
|
||||
return debug_verbose_not(not, f);
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
Event::Task(Task::Respond(resp)) => {
|
||||
return f
|
||||
.debug_struct("RawResponse")
|
||||
.debug_struct("Response")
|
||||
.field("id", &resp.id)
|
||||
.field("error", &resp.error)
|
||||
.finish();
|
||||
|
@ -194,8 +196,8 @@ impl fmt::Debug for Event {
|
|||
|
||||
fn main_loop_inner(
|
||||
pool: &ThreadPool,
|
||||
msg_sender: &Sender<RawMessage>,
|
||||
msg_receiver: &Receiver<RawMessage>,
|
||||
msg_sender: &Sender<Message>,
|
||||
msg_receiver: &Receiver<Message>,
|
||||
task_sender: Sender<Task>,
|
||||
task_receiver: Receiver<Task>,
|
||||
state: &mut WorldState,
|
||||
|
@ -249,10 +251,9 @@ fn main_loop_inner(
|
|||
in_flight_libraries -= 1;
|
||||
}
|
||||
Event::Msg(msg) => match msg {
|
||||
RawMessage::Request(req) => {
|
||||
let req = match handle_shutdown(req, msg_sender) {
|
||||
Some(req) => req,
|
||||
None => return Ok(()),
|
||||
Message::Request(req) => {
|
||||
if handle_shutdown(&req, msg_sender) {
|
||||
return Ok(());
|
||||
};
|
||||
on_request(
|
||||
state,
|
||||
|
@ -264,11 +265,11 @@ fn main_loop_inner(
|
|||
req,
|
||||
)?
|
||||
}
|
||||
RawMessage::Notification(not) => {
|
||||
Message::Notification(not) => {
|
||||
on_notification(msg_sender, state, pending_requests, &mut subs, not)?;
|
||||
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(
|
||||
task: Task,
|
||||
msg_sender: &Sender<RawMessage>,
|
||||
msg_sender: &Sender<Message>,
|
||||
pending_requests: &mut PendingRequests,
|
||||
state: &mut WorldState,
|
||||
) {
|
||||
match task {
|
||||
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);
|
||||
state.complete_request(completed);
|
||||
msg_sender.send(response.into()).unwrap();
|
||||
|
@ -336,9 +337,9 @@ fn on_request(
|
|||
pending_requests: &mut PendingRequests,
|
||||
pool: &ThreadPool,
|
||||
sender: &Sender<Task>,
|
||||
msg_sender: &Sender<RawMessage>,
|
||||
msg_sender: &Sender<Message>,
|
||||
request_received: Instant,
|
||||
req: RawRequest,
|
||||
req: Request,
|
||||
) -> Result<()> {
|
||||
let mut pool_dispatcher = PoolDispatcher {
|
||||
req: Some(req),
|
||||
|
@ -388,22 +389,20 @@ fn on_request(
|
|||
}
|
||||
|
||||
fn on_notification(
|
||||
msg_sender: &Sender<RawMessage>,
|
||||
msg_sender: &Sender<Message>,
|
||||
state: &mut WorldState,
|
||||
pending_requests: &mut PendingRequests,
|
||||
subs: &mut Subscriptions,
|
||||
not: RawNotification,
|
||||
not: Notification,
|
||||
) -> Result<()> {
|
||||
let not = match not.cast::<req::Cancel>() {
|
||||
let not = match notification_cast::<req::Cancel>(not) {
|
||||
Ok(params) => {
|
||||
let id = match params.id {
|
||||
NumberOrString::Number(id) => id,
|
||||
NumberOrString::String(id) => {
|
||||
panic!("string id's not supported: {:?}", id);
|
||||
}
|
||||
let id: RequestId = match params.id {
|
||||
NumberOrString::Number(id) => id.into(),
|
||||
NumberOrString::String(id) => id.into(),
|
||||
};
|
||||
if pending_requests.cancel(id) {
|
||||
let response = RawResponse::err(
|
||||
if pending_requests.cancel(&id) {
|
||||
let response = Response::new_err(
|
||||
id,
|
||||
ErrorCode::RequestCanceled as i32,
|
||||
"canceled by client".to_string(),
|
||||
|
@ -414,7 +413,7 @@ fn on_notification(
|
|||
}
|
||||
Err(not) => not,
|
||||
};
|
||||
let not = match not.cast::<req::DidOpenTextDocument>() {
|
||||
let not = match notification_cast::<req::DidOpenTextDocument>(not) {
|
||||
Ok(params) => {
|
||||
let uri = params.text_document.uri;
|
||||
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
||||
|
@ -427,7 +426,7 @@ fn on_notification(
|
|||
}
|
||||
Err(not) => not,
|
||||
};
|
||||
let not = match not.cast::<req::DidChangeTextDocument>() {
|
||||
let not = match notification_cast::<req::DidChangeTextDocument>(not) {
|
||||
Ok(mut params) => {
|
||||
let uri = params.text_document.uri;
|
||||
let path = uri.to_file_path().map_err(|()| format!("invalid uri: {}", uri))?;
|
||||
|
@ -438,7 +437,7 @@ fn on_notification(
|
|||
}
|
||||
Err(not) => not,
|
||||
};
|
||||
let not = match not.cast::<req::DidCloseTextDocument>() {
|
||||
let not = match notification_cast::<req::DidCloseTextDocument>(not) {
|
||||
Ok(params) => {
|
||||
let uri = params.text_document.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));
|
||||
}
|
||||
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();
|
||||
return Ok(());
|
||||
}
|
||||
Err(not) => not,
|
||||
};
|
||||
let not = match not.cast::<req::DidChangeConfiguration>() {
|
||||
let not = match notification_cast::<req::DidChangeConfiguration>(not) {
|
||||
Ok(_params) => {
|
||||
return Ok(());
|
||||
}
|
||||
|
@ -463,11 +462,11 @@ fn on_notification(
|
|||
}
|
||||
|
||||
struct PoolDispatcher<'a> {
|
||||
req: Option<RawRequest>,
|
||||
req: Option<Request>,
|
||||
pool: &'a ThreadPool,
|
||||
world: &'a mut WorldState,
|
||||
pending_requests: &'a mut PendingRequests,
|
||||
msg_sender: &'a Sender<RawMessage>,
|
||||
msg_sender: &'a Sender<Message>,
|
||||
sender: &'a Sender<Task>,
|
||||
request_received: Instant,
|
||||
}
|
||||
|
@ -522,13 +521,13 @@ impl<'a> PoolDispatcher<'a> {
|
|||
Ok(self)
|
||||
}
|
||||
|
||||
fn parse<R>(&mut self) -> Option<(u64, R::Params)>
|
||||
fn parse<R>(&mut self) -> Option<(RequestId, R::Params)>
|
||||
where
|
||||
R: req::Request + 'static,
|
||||
R::Params: DeserializeOwned + Send + 'static,
|
||||
{
|
||||
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,
|
||||
Err(req) => {
|
||||
self.req = Some(req);
|
||||
|
@ -536,7 +535,7 @@ impl<'a> PoolDispatcher<'a> {
|
|||
}
|
||||
};
|
||||
self.pending_requests.start(PendingRequest {
|
||||
id,
|
||||
id: id.clone(),
|
||||
method: R::METHOD.to_string(),
|
||||
received: self.request_received,
|
||||
});
|
||||
|
@ -548,7 +547,7 @@ impl<'a> PoolDispatcher<'a> {
|
|||
None => (),
|
||||
Some(req) => {
|
||||
log::error!("unknown request: {:?}", req);
|
||||
let resp = RawResponse::err(
|
||||
let resp = Response::new_err(
|
||||
req.id,
|
||||
ErrorCode::MethodNotFound as i32,
|
||||
"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
|
||||
R: req::Request + 'static,
|
||||
R::Params: DeserializeOwned + Send + 'static,
|
||||
R::Result: Serialize + 'static,
|
||||
{
|
||||
let response = match result {
|
||||
Ok(resp) => RawResponse::ok::<R>(id, &resp),
|
||||
Ok(resp) => Response::new_ok(id, &resp),
|
||||
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) => {
|
||||
if is_canceled(&e) {
|
||||
// FIXME: When https://github.com/Microsoft/vscode-languageserver-node/issues/457
|
||||
// gets fixed, we can return the proper response.
|
||||
// This works around the issue where "content modified" error would continuously
|
||||
// show an message pop-up in VsCode
|
||||
// RawResponse::err(
|
||||
// Response::err(
|
||||
// id,
|
||||
// ErrorCode::ContentModified as i32,
|
||||
// "content modified".to_string(),
|
||||
// )
|
||||
RawResponse {
|
||||
id,
|
||||
result: Some(serde_json::to_value(&()).unwrap()),
|
||||
error: None,
|
||||
}
|
||||
Response::new_ok(id, ())
|
||||
} 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) => {
|
||||
let not = RawNotification::new::<req::PublishDiagnostics>(¶ms);
|
||||
let not = notification_new::<req::PublishDiagnostics>(params);
|
||||
sender.send(Task::Notify(not)).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -626,7 +621,7 @@ fn update_file_notifications_on_threadpool(
|
|||
}
|
||||
}
|
||||
Ok(params) => {
|
||||
let not = RawNotification::new::<req::PublishDecorations>(¶ms);
|
||||
let not = notification_new::<req::PublishDecorations>(params);
|
||||
sender.send(Task::Notify(not)).unwrap();
|
||||
}
|
||||
}
|
||||
|
@ -635,17 +630,33 @@ fn update_file_notifications_on_threadpool(
|
|||
});
|
||||
}
|
||||
|
||||
pub fn show_message(
|
||||
typ: req::MessageType,
|
||||
message: impl Into<String>,
|
||||
sender: &Sender<RawMessage>,
|
||||
) {
|
||||
pub fn show_message(typ: req::MessageType, message: impl Into<String>, sender: &Sender<Message>) {
|
||||
let message = message.into();
|
||||
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();
|
||||
}
|
||||
|
||||
fn is_canceled(e: &Box<dyn std::error::Error + Send + Sync>) -> bool {
|
||||
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 gen_lsp_server::ErrorCode;
|
||||
use lsp_server::ErrorCode;
|
||||
use lsp_types::{
|
||||
CodeAction, CodeActionResponse, CodeLens, Command, CompletionItem, Diagnostic,
|
||||
DocumentFormattingParams, DocumentHighlight, DocumentSymbol, FoldingRange, FoldingRangeKind,
|
||||
|
|
|
@ -1,17 +1,18 @@
|
|||
use std::time::{Duration, Instant};
|
||||
|
||||
use lsp_server::RequestId;
|
||||
use rustc_hash::FxHashMap;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct CompletedRequest {
|
||||
pub id: u64,
|
||||
pub id: RequestId,
|
||||
pub method: String,
|
||||
pub duration: Duration,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PendingRequest {
|
||||
pub(crate) id: u64,
|
||||
pub(crate) id: RequestId,
|
||||
pub(crate) method: String,
|
||||
pub(crate) received: Instant,
|
||||
}
|
||||
|
@ -28,20 +29,20 @@ impl From<PendingRequest> for CompletedRequest {
|
|||
|
||||
#[derive(Debug, Default)]
|
||||
pub(crate) struct PendingRequests {
|
||||
map: FxHashMap<u64, PendingRequest>,
|
||||
map: FxHashMap<RequestId, PendingRequest>,
|
||||
}
|
||||
|
||||
impl PendingRequests {
|
||||
pub(crate) fn start(&mut self, request: PendingRequest) {
|
||||
let id = request.id;
|
||||
let prev = self.map.insert(id, request);
|
||||
let id = request.id.clone();
|
||||
let prev = self.map.insert(id.clone(), request);
|
||||
assert!(prev.is_none(), "duplicate request with id {}", id);
|
||||
}
|
||||
pub(crate) fn cancel(&mut self, id: u64) -> bool {
|
||||
self.map.remove(&id).is_some()
|
||||
pub(crate) fn cancel(&mut self, id: &RequestId) -> bool {
|
||||
self.map.remove(id).is_some()
|
||||
}
|
||||
pub(crate) fn finish(&mut self, id: u64) -> Option<CompletedRequest> {
|
||||
self.map.remove(&id).map(CompletedRequest::from)
|
||||
pub(crate) fn finish(&mut self, id: &RequestId) -> Option<CompletedRequest> {
|
||||
self.map.remove(id).map(CompletedRequest::from)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@ use std::{
|
|||
};
|
||||
|
||||
use crossbeam_channel::{unbounded, Receiver};
|
||||
use gen_lsp_server::ErrorCode;
|
||||
use lsp_server::ErrorCode;
|
||||
use lsp_types::Url;
|
||||
use parking_lot::RwLock;
|
||||
use ra_ide_api::{
|
||||
|
|
|
@ -8,13 +8,10 @@ use std::{
|
|||
|
||||
use crossbeam_channel::{after, select, Receiver};
|
||||
use flexi_logger::Logger;
|
||||
use gen_lsp_server::{RawMessage, RawNotification, RawRequest};
|
||||
use lsp_server::{Message, Notification, Request};
|
||||
use lsp_types::{
|
||||
notification::DidOpenTextDocument,
|
||||
notification::{Notification, ShowMessage},
|
||||
request::{Request, Shutdown},
|
||||
ClientCapabilities, DidOpenTextDocumentParams, GotoCapability, TextDocumentClientCapabilities,
|
||||
TextDocumentIdentifier, TextDocumentItem, Url,
|
||||
request::Shutdown, ClientCapabilities, DidOpenTextDocumentParams, GotoCapability,
|
||||
TextDocumentClientCapabilities, TextDocumentIdentifier, TextDocumentItem, Url,
|
||||
};
|
||||
use serde::Serialize;
|
||||
use serde_json::{to_string_pretty, Value};
|
||||
|
@ -84,9 +81,9 @@ pub fn project(fixture: &str) -> Server {
|
|||
|
||||
pub struct Server {
|
||||
req_id: Cell<u64>,
|
||||
messages: RefCell<Vec<RawMessage>>,
|
||||
messages: RefCell<Vec<Message>>,
|
||||
dir: TempDir,
|
||||
worker: Worker<RawMessage, RawMessage>,
|
||||
worker: Worker<Message, Message>,
|
||||
}
|
||||
|
||||
impl Server {
|
||||
|
@ -100,7 +97,7 @@ impl Server {
|
|||
|
||||
let roots = if roots.is_empty() { vec![path] } else { roots };
|
||||
|
||||
let worker = Worker::<RawMessage, RawMessage>::spawn(
|
||||
let worker = Worker::<Message, Message>::spawn(
|
||||
"test server",
|
||||
128,
|
||||
move |msg_receiver, msg_sender| {
|
||||
|
@ -128,7 +125,8 @@ impl Server {
|
|||
let res = Server { req_id: Cell::new(1), dir, messages: Default::default(), worker };
|
||||
|
||||
for (path, text) in files {
|
||||
res.send_notification(RawNotification::new::<DidOpenTextDocument>(
|
||||
res.send_notification(Notification::new(
|
||||
"textDocument/didOpen".to_string(),
|
||||
&DidOpenTextDocumentParams {
|
||||
text_document: TextDocumentItem {
|
||||
uri: Url::from_file_path(path).unwrap(),
|
||||
|
@ -149,16 +147,16 @@ impl Server {
|
|||
|
||||
pub fn notification<N>(&self, params: N::Params)
|
||||
where
|
||||
N: Notification,
|
||||
N: lsp_types::notification::Notification,
|
||||
N::Params: Serialize,
|
||||
{
|
||||
let r = RawNotification::new::<N>(¶ms);
|
||||
let r = Notification::new(N::METHOD.to_string(), params);
|
||||
self.send_notification(r)
|
||||
}
|
||||
|
||||
pub fn request<R>(&self, params: R::Params, expected_resp: Value)
|
||||
where
|
||||
R: Request,
|
||||
R: lsp_types::request::Request,
|
||||
R::Params: Serialize,
|
||||
{
|
||||
let actual = self.send_request::<R>(params);
|
||||
|
@ -175,23 +173,23 @@ impl Server {
|
|||
|
||||
pub fn send_request<R>(&self, params: R::Params) -> Value
|
||||
where
|
||||
R: Request,
|
||||
R: lsp_types::request::Request,
|
||||
R::Params: Serialize,
|
||||
{
|
||||
let id = self.req_id.get();
|
||||
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)
|
||||
}
|
||||
fn send_request_(&self, r: RawRequest) -> Value {
|
||||
let id = r.id;
|
||||
self.worker.sender().send(RawMessage::Request(r)).unwrap();
|
||||
fn send_request_(&self, r: Request) -> Value {
|
||||
let id = r.id.clone();
|
||||
self.worker.sender().send(r.into()).unwrap();
|
||||
while let Some(msg) = self.recv() {
|
||||
match msg {
|
||||
RawMessage::Request(req) => panic!("unexpected request: {:?}", req),
|
||||
RawMessage::Notification(_) => (),
|
||||
RawMessage::Response(res) => {
|
||||
Message::Request(req) => panic!("unexpected request: {:?}", req),
|
||||
Message::Notification(_) => (),
|
||||
Message::Response(res) => {
|
||||
assert_eq!(res.id, id);
|
||||
if let Some(err) = res.error {
|
||||
panic!("error response: {:#?}", err);
|
||||
|
@ -203,15 +201,16 @@ impl Server {
|
|||
panic!("no response");
|
||||
}
|
||||
pub fn wait_until_workspace_is_loaded(&self) {
|
||||
self.wait_for_message_cond(1, &|msg: &RawMessage| match msg {
|
||||
RawMessage::Notification(n) if n.method == ShowMessage::METHOD => {
|
||||
let msg = n.clone().cast::<req::ShowMessage>().unwrap();
|
||||
self.wait_for_message_cond(1, &|msg: &Message| match msg {
|
||||
Message::Notification(n) if n.method == "window/showMessage" => {
|
||||
let msg =
|
||||
n.clone().extract::<req::ShowMessageParams>("window/showMessage").unwrap();
|
||||
msg.message.starts_with("workspace loaded")
|
||||
}
|
||||
_ => 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;
|
||||
for msg in self.messages.borrow().iter() {
|
||||
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| {
|
||||
self.messages.borrow_mut().push(msg.clone());
|
||||
msg
|
||||
})
|
||||
}
|
||||
fn send_notification(&self, not: RawNotification) {
|
||||
self.worker.sender().send(RawMessage::Notification(not)).unwrap();
|
||||
fn send_notification(&self, not: Notification) {
|
||||
self.worker.sender().send(Message::Notification(not)).unwrap();
|
||||
}
|
||||
|
||||
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);
|
||||
select! {
|
||||
recv(receiver) -> msg => msg.ok(),
|
||||
|
|
Loading…
Reference in a new issue