Skip to content

Commit

Permalink
feat(language server): removed async code and switched to lsp_server
Browse files Browse the repository at this point in the history
  • Loading branch information
baszalmstra committed Jan 12, 2021
1 parent 8a6e9b4 commit 2b08ebe
Show file tree
Hide file tree
Showing 14 changed files with 460 additions and 778 deletions.
6 changes: 3 additions & 3 deletions crates/mun_language_server/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,12 @@ categories = ["game-development", "mun"]

[dependencies]
rustc-hash="1.1.0"
lsp-types = "0.74"
lsp-types = "0.86.0"
lsp-server = "0.5.0"
log = "0.4"
serde = "1.0"
serde_json = "1.0"
serde_derive = "1.0"
async-std = "1.6"
futures = "0.3"
anyhow = "1.0"
thiserror = "1.0"
salsa = "0.15.0"
Expand All @@ -35,6 +34,7 @@ mun_target = { version = "=0.2.0", path = "../mun_target" }
mun_syntax = { version = "=0.2.0", path = "../mun_syntax" }
mun_diagnostics = { version = "=0.1.0", path = "../mun_diagnostics" }
crossbeam-channel = "0.5.0"
parking_lot="0.11.1"
paths = {path="../mun_paths", package="mun_paths"}

[dev-dependencies]
Expand Down
7 changes: 7 additions & 0 deletions crates/mun_language_server/src/cancelation.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use std::error::Error;

/// An error signifying a cancelled operation.
pub struct Canceled {
// This is here so that you cannot construct a Canceled
Expand Down Expand Up @@ -29,3 +31,8 @@ impl std::fmt::Debug for Canceled {
}

impl std::error::Error for Canceled {}

/// Returns true if the specified error is of type [`Canceled`]
pub(crate) fn is_canceled(e: &(dyn Error + 'static)) -> bool {
e.downcast_ref::<Canceled>().is_some()
}
4 changes: 2 additions & 2 deletions crates/mun_language_server/src/conversion.rs
Original file line number Diff line number Diff line change
Expand Up @@ -63,8 +63,8 @@ pub fn convert_unit(
) -> lsp_types::Position {
let line_col = line_index.line_col(range);
lsp_types::Position {
line: line_col.line.into(),
character: line_col.col.into(),
line: line_col.line,
character: line_col.col,
}
}

Expand Down
164 changes: 164 additions & 0 deletions crates/mun_language_server/src/dispatcher.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
use crate::cancelation::is_canceled;
use crate::from_json;
use crate::main_loop::LanguageServerState;
use anyhow::Result;
use serde::de::DeserializeOwned;
use serde::Serialize;

/// A helper struct to ergonomically dispatch LSP requests to functions.
pub(crate) struct RequestDispatcher<'a> {
state: &'a mut LanguageServerState,
request: Option<lsp_server::Request>,
}

impl<'a> RequestDispatcher<'a> {
/// Constructs a new dispatcher for the specified request
pub fn new(state: &'a mut LanguageServerState, request: lsp_server::Request) -> Self {
RequestDispatcher {
state,
request: Some(request),
}
}

/// Try to dispatch the event as the given Request type.
pub fn on<R>(
&mut self,
f: fn(&mut LanguageServerState, R::Params) -> Result<R::Result>,
) -> Result<&mut Self>
where
R: lsp_types::request::Request + 'static,
R::Params: DeserializeOwned + 'static,
R::Result: Serialize + 'static,
{
let (id, params) = match self.parse::<R>() {
Some(it) => it,
None => return Ok(self),
};

let result = f(self.state, params);
let response = result_to_response::<R>(id, result);
self.state.respond(response);
Ok(self)
}

/// Tries to parse the request as the specified type. If the request is of the specified type,
/// the request is transferred and any subsequent call to this method will return None. If an
/// error is encountered during parsing of the request parameters an error is send to the
/// client.
fn parse<R>(&mut self) -> Option<(lsp_server::RequestId, R::Params)>
where
R: lsp_types::request::Request + 'static,
R::Params: DeserializeOwned + 'static,
{
let req = match &self.request {
Some(req) if req.method == R::METHOD => self.request.take().unwrap(),
_ => return None,
};

match from_json(R::METHOD, req.params) {
Ok(params) => Some((req.id, params)),
Err(err) => {
let response = lsp_server::Response::new_err(
req.id,
lsp_server::ErrorCode::InvalidParams as i32,
err.to_string(),
);
self.state.respond(response);
None
}
}
}

/// Wraps-up the dispatcher. If the the request was not handled, report back that this is an
/// unknown request.
pub fn finish(&mut self) {
if let Some(req) = self.request.take() {
log::error!("unknown request: {:?}", req);
let response = lsp_server::Response::new_err(
req.id,
lsp_server::ErrorCode::MethodNotFound as i32,
"unknown request".to_string(),
);
self.state.respond(response);
}
}
}

pub(crate) struct NotificationDispatcher<'a> {
state: &'a mut LanguageServerState,
notification: Option<lsp_server::Notification>,
}

impl<'a> NotificationDispatcher<'a> {
/// Constructs a new dispatcher for the specified request
pub fn new(state: &'a mut LanguageServerState, notification: lsp_server::Notification) -> Self {
NotificationDispatcher {
state,
notification: Some(notification),
}
}

/// Try to dispatch the event as the given Notification type.
pub fn on<N>(
&mut self,
f: fn(&mut LanguageServerState, N::Params) -> Result<()>,
) -> Result<&mut Self>
where
N: lsp_types::notification::Notification + 'static,
N::Params: DeserializeOwned + Send + 'static,
{
let notification = match self.notification.take() {
Some(it) => it,
None => return Ok(self),
};
let params = match notification.extract::<N::Params>(N::METHOD) {
Ok(it) => it,
Err(notification) => {
self.notification = Some(notification);
return Ok(self);
}
};
f(self.state, params)?;
Ok(self)
}

/// Wraps-up the dispatcher. If the notification was not handled, log an error.
pub fn finish(&mut self) {
if let Some(notification) = &self.notification {
if !notification.method.starts_with("$/") {
log::error!("unhandled notification: {:?}", notification);
}
}
}
}

/// Converts the specified results of an LSP request into an LSP response handling any errors that
/// may have occurred.
fn result_to_response<R>(
id: lsp_server::RequestId,
result: Result<R::Result>,
) -> lsp_server::Response
where
R: lsp_types::request::Request + 'static,
R::Params: DeserializeOwned + 'static,
R::Result: Serialize + 'static,
{
match result {
Ok(resp) => lsp_server::Response::new_ok(id, &resp),
Err(e) => {
if is_canceled(&*e) {
lsp_server::Response::new_err(
id,
lsp_server::ErrorCode::ContentModified as i32,
"content modified".to_string(),
)
} else {
lsp_server::Response::new_err(
id,
lsp_server::ErrorCode::InternalError as i32,
e.to_string(),
)
}
}
}
}
20 changes: 7 additions & 13 deletions crates/mun_language_server/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ mod config;
mod conversion;
mod db;
mod diagnostics;
mod dispatcher;
mod main_loop;
mod project_manifest;
pub mod protocol;
mod workspace;

pub use config::Config;
Expand All @@ -33,14 +33,14 @@ pub fn to_json<T: Serialize>(value: T) -> Result<serde_json::Value> {
}

/// Main entry point for the language server
pub async fn run_server_async() -> Result<()> {
pub fn run_server() -> Result<()> {
log::info!("language server started");

// Setup IO connections
let mut connection = protocol::Connection::stdio();
let (connection, io_threads) = lsp_server::Connection::stdio();

// Wait for a client to connect
let (initialize_id, initialize_params) = connection.initialize_start().await?;
let (initialize_id, initialize_params) = connection.initialize_start()?;

let initialize_params =
from_json::<lsp_types::InitializeParams>("InitializeParams", initialize_params)?;
Expand All @@ -57,9 +57,7 @@ pub async fn run_server_async() -> Result<()> {

let initialize_result = serde_json::to_value(initialize_result).unwrap();

connection
.initialize_finish(initialize_id, initialize_result)
.await?;
connection.initialize_finish(initialize_id, initialize_result)?;

if let Some(client_info) = initialize_params.client_info {
log::info!(
Expand Down Expand Up @@ -122,12 +120,8 @@ pub async fn run_server_async() -> Result<()> {
config
};

main_loop(connection, config).await?;
main_loop(connection, config)?;

io_threads.join()?;
Ok(())
}

/// Main entry point for the language server
pub fn run_server() -> Result<()> {
async_std::task::block_on(run_server_async())
}
Loading

0 comments on commit 2b08ebe

Please sign in to comment.