move lsp-server to a separate repository

This commit is contained in:
Aleksey Kladov 2019-08-30 17:24:11 +03:00
parent 7d72ca8000
commit 72a3722470
14 changed files with 141 additions and 725 deletions

46
Cargo.lock generated
View file

@ -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"

View file

@ -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"

View file

@ -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(())
}

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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(())
}

View file

@ -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")?,
}
}
}

View file

@ -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" }

View file

@ -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(())
}

View file

@ -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", &not.method).finish()
let debug_verbose_not = |not: &Notification, f: &mut fmt::Formatter| {
f.debug_struct("Notification").field("method", &not.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>(&params);
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>(&params);
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>(&params);
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>(&params);
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)
}

View file

@ -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,

View file

@ -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)
}
}

View file

@ -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::{

View file

@ -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>(&params);
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, &params);
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(),