use ls_types::*;
use serde::Deserialize;
use serde_json::{Value, json};
use wasm_bindgen::prelude::*;
use crate::wasm::server::WasmCore;
const METHOD_NOT_FOUND: i64 = -32601;
const INVALID_REQUEST: i64 = -32600;
const INVALID_PARAMS: i64 = -32602;
#[derive(Deserialize)]
struct Incoming {
#[serde(default)]
id: Option<Value>,
method: String,
#[serde(default)]
params: Value,
}
pub async fn handle_message(core: &WasmCore, json_text: &str) -> Result<JsValue, JsValue> {
let message: Incoming = match serde_json::from_str(json_text) {
Ok(value) => value,
Err(error) => {
return Ok(JsValue::from_str(&error_response_string(
None,
INVALID_REQUEST,
&format!("invalid JSON-RPC payload: {error}"),
)));
}
};
let is_request = message.id.is_some();
match dispatch(core, &message.method, message.params).await {
Outcome::Notification => Ok(JsValue::UNDEFINED),
Outcome::Response(value) => {
if !is_request {
return Ok(JsValue::UNDEFINED);
}
Ok(JsValue::from_str(&success_response_string(
message.id.unwrap_or(Value::Null),
value,
)))
}
Outcome::Error { code, message: msg } => {
if !is_request {
return Ok(JsValue::UNDEFINED);
}
Ok(JsValue::from_str(&error_response_string(
Some(message.id.unwrap_or(Value::Null)),
code,
&msg,
)))
}
}
}
enum Outcome {
Notification,
Response(Value),
Error { code: i64, message: String },
}
impl Outcome {
fn from_value<T: serde::Serialize>(value: T) -> Self {
match serde_json::to_value(value) {
Ok(value) => Outcome::Response(value),
Err(error) => Outcome::Error {
code: -32603,
message: format!("failed to serialise response: {error}"),
},
}
}
}
async fn dispatch(core: &WasmCore, method: &str, params: Value) -> Outcome {
match method {
"initialize" => match decode::<InitializeParams>(params) {
Ok(params) => Outcome::from_value(core.initialize(params).await),
Err(error) => error,
},
"initialized" => {
core.initialized().await;
Outcome::Notification
}
"shutdown" => Outcome::Response(Value::Null),
"exit" => Outcome::Notification,
"textDocument/didOpen" => match decode::<DidOpenTextDocumentParams>(params) {
Ok(params) => {
core.did_open(params).await;
Outcome::Notification
}
Err(error) => error,
},
"textDocument/didChange" => match decode::<DidChangeTextDocumentParams>(params) {
Ok(params) => {
core.did_change(params).await;
Outcome::Notification
}
Err(error) => error,
},
"textDocument/didSave" => match decode::<DidSaveTextDocumentParams>(params) {
Ok(params) => {
core.did_save(params).await;
Outcome::Notification
}
Err(error) => error,
},
"textDocument/didClose" => match decode::<DidCloseTextDocumentParams>(params) {
Ok(params) => {
core.did_close(params).await;
Outcome::Notification
}
Err(error) => error,
},
"workspace/didChangeConfiguration" => {
match decode::<DidChangeConfigurationParams>(params) {
Ok(params) => {
core.did_change_configuration(params).await;
Outcome::Notification
}
Err(error) => error,
}
}
"workspace/didChangeWorkspaceFolders" => {
match decode::<DidChangeWorkspaceFoldersParams>(params) {
Ok(params) => {
core.did_change_workspace_folders(params).await;
Outcome::Notification
}
Err(error) => error,
}
}
"workspace/symbol" => match decode::<WorkspaceSymbolParams>(params) {
Ok(params) => Outcome::from_value(core.workspace_symbol(params).await),
Err(error) => error,
},
"textDocument/completion" => match decode::<CompletionParams>(params) {
Ok(params) => Outcome::from_value(core.completion(params).await),
Err(error) => error,
},
"textDocument/hover" => match decode::<HoverParams>(params) {
Ok(params) => Outcome::from_value(core.hover(params).await),
Err(error) => error,
},
"textDocument/documentSymbol" => match decode::<DocumentSymbolParams>(params) {
Ok(params) => Outcome::from_value(core.document_symbol(params).await),
Err(error) => error,
},
"textDocument/definition" => match decode::<GotoDefinitionParams>(params) {
Ok(params) => Outcome::from_value(core.goto_definition(params).await),
Err(error) => error,
},
"textDocument/references" => match decode::<ReferenceParams>(params) {
Ok(params) => Outcome::from_value(core.references(params).await),
Err(error) => error,
},
"textDocument/prepareRename" => match decode::<TextDocumentPositionParams>(params) {
Ok(params) => Outcome::from_value(core.prepare_rename(params).await),
Err(error) => error,
},
"textDocument/rename" => match decode::<RenameParams>(params) {
Ok(params) => Outcome::from_value(core.rename(params).await),
Err(error) => error,
},
"textDocument/signatureHelp" => match decode::<SignatureHelpParams>(params) {
Ok(params) => Outcome::from_value(core.signature_help(params).await),
Err(error) => error,
},
"textDocument/codeAction" => match decode::<CodeActionParams>(params) {
Ok(params) => Outcome::from_value(core.code_action(params).await),
Err(error) => error,
},
"textDocument/documentHighlight" => match decode::<DocumentHighlightParams>(params) {
Ok(params) => Outcome::from_value(core.document_highlight(params).await),
Err(error) => error,
},
"textDocument/inlayHint" => match decode::<InlayHintParams>(params) {
Ok(params) => Outcome::from_value(core.inlay_hint(params).await),
Err(error) => error,
},
"textDocument/prepareCallHierarchy" => match decode::<CallHierarchyPrepareParams>(params) {
Ok(params) => Outcome::from_value(core.prepare_call_hierarchy(params).await),
Err(error) => error,
},
"callHierarchy/incomingCalls" => match decode::<CallHierarchyIncomingCallsParams>(params) {
Ok(params) => Outcome::from_value(core.incoming_calls(params).await),
Err(error) => error,
},
"callHierarchy/outgoingCalls" => match decode::<CallHierarchyOutgoingCallsParams>(params) {
Ok(params) => Outcome::from_value(core.outgoing_calls(params).await),
Err(error) => error,
},
_ => Outcome::Error {
code: METHOD_NOT_FOUND,
message: format!("unknown LSP method `{method}`"),
},
}
}
fn decode<T: serde::de::DeserializeOwned>(params: Value) -> Result<T, Outcome> {
serde_json::from_value(params).map_err(|error| Outcome::Error {
code: INVALID_PARAMS,
message: format!("invalid params: {error}"),
})
}
fn success_response_string(id: Value, result: Value) -> String {
json!({
"jsonrpc": "2.0",
"id": id,
"result": result,
})
.to_string()
}
fn error_response_string(id: Option<Value>, code: i64, message: &str) -> String {
json!({
"jsonrpc": "2.0",
"id": id.unwrap_or(Value::Null),
"error": {
"code": code,
"message": message,
},
})
.to_string()
}