From b14a00c7bb5b24328035d97c064be9b3367d979d Mon Sep 17 00:00:00 2001 From: Shunsuke Shibayama Date: Fri, 8 Sep 2023 02:45:47 +0900 Subject: [PATCH] build: move molc to a separate repository --- Cargo.lock | 2 + Cargo.toml | 2 - crates/els/Cargo.toml | 2 +- crates/molc/Cargo.toml | 19 -- crates/molc/README.md | 69 ------ crates/molc/src/lib.rs | 466 ------------------------------------ crates/molc/src/messages.rs | 79 ------ 7 files changed, 3 insertions(+), 636 deletions(-) delete mode 100644 crates/molc/Cargo.toml delete mode 100644 crates/molc/README.md delete mode 100644 crates/molc/src/lib.rs delete mode 100644 crates/molc/src/messages.rs diff --git a/Cargo.lock b/Cargo.lock index 1d6713b0..12b2910b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -248,6 +248,8 @@ dependencies = [ [[package]] name = "molc" version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19b669aab31ca7552fc43cb9ab08e325113aa090f7bf97a2112b3d6241ba898" dependencies = [ "lsp-types", "serde", diff --git a/Cargo.toml b/Cargo.toml index f391b45b..a2fffc8d 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -17,7 +17,6 @@ members = [ "crates/erg_compiler", "crates/erg_parser", "crates/els", - "crates/molc" ] [workspace.package] @@ -69,7 +68,6 @@ erg_common = { version = "0.6.20-nightly.2", path = "./crates/erg_common" } erg_parser = { version = "0.6.20-nightly.2", path = "./crates/erg_parser" } erg_compiler = { version = "0.6.20-nightly.2", path = "./crates/erg_compiler" } els = { version = "0.1.32-nightly.2", path = "./crates/els" } -molc = { version = "0.1.0", path = "./crates/molc" } [dependencies] erg_common = { workspace = true } diff --git a/crates/els/Cargo.toml b/crates/els/Cargo.toml index 13987099..a38bef92 100644 --- a/crates/els/Cargo.toml +++ b/crates/els/Cargo.toml @@ -23,7 +23,7 @@ experimental = ["erg_common/experimental", "erg_compiler/experimental"] [dependencies] erg_common = { workspace = true, features = ["els"] } erg_compiler = { workspace = true, features = ["els"] } -molc = { workspace = true } +molc = { version = "0.1.0" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.85" lsp-types = { version = "0.93.2", features = ["proposed"] } diff --git a/crates/molc/Cargo.toml b/crates/molc/Cargo.toml deleted file mode 100644 index 7caddaed..00000000 --- a/crates/molc/Cargo.toml +++ /dev/null @@ -1,19 +0,0 @@ -[package] -name = "molc" -version = "0.1.0" -description = "A mock language client for testing language servers" -authors.workspace = true -license.workspace = true -edition.workspace = true -repository.workspace = true -homepage.workspace = true - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -serde = { version = "1.0", features = ["derive"] } -serde_json = "1.0.85" -lsp-types = { version = "0.93.2", features = ["proposed"] } - -[path] -lib = "src/lib.rs" diff --git a/crates/molc/README.md b/crates/molc/README.md deleted file mode 100644 index 2c74106a..00000000 --- a/crates/molc/README.md +++ /dev/null @@ -1,69 +0,0 @@ -# `molc` - -`molc` is a mock (fake) language client for testing language servers. - -## Usage - -You can see specific examples of molc use in [ELS](https://github.com/erg-lang/erg/tree/main/crates/els). - -```rust -use lsp_types::{Url, Value}; - -use molc::{FakeClient, LangServer, RedirectableStdout}; -use molc::oneline_range; - -pub struct Server { - stdout_redirect: Option>, - ... -} - -impl LangServer for Server { - fn dispatch(&mut self, msg: impl Into) -> Result<(), Box> { - self.dispatch(msg) - } -} - -impl RedirectableStdout for Server { - fn sender(&self) -> Option<&std::sync::mpsc::Sender> { - self.stdout_redirect.as_ref() - } -} - -impl Server { - fn bind_fake_client() -> FakeClient { - // The server should send responses to this channel at least during testing. - let (sender, receiver) = std::sync::mpsc::channel(); - DummyClient::new( - Server::new(Some(sender)), - receiver, - ) - } - - fn init(&mut self, msg: &Value, id: i64) -> ELSResult<()> { - self.send_log("initializing the language server")?; - let result = InitializeResult { - ... - }; - self.init_services(); - self.send_stdout(&json!({ - "jsonrpc": "2.0", - "id": id, - "result": result, - })) - } - - ... -} - -#[test] -fn test_references() -> Result<(), Box> { - let mut client = Server::bind_fake_client(); - client.request_initialize()?; - let uri = Url::from_file_path(Path::new(FILE_A).canonicalize()?).unwrap(); - client.notify_open(FILE_A)?; - let locations = client.request_references(uri, 1, 4)?.unwrap(); - assert_eq!(locations.len(), 1); - assert_eq!(&locations[0].range, &oneline_range(1, 4, 5)); - Ok(()) -} -``` diff --git a/crates/molc/src/lib.rs b/crates/molc/src/lib.rs deleted file mode 100644 index 4477f485..00000000 --- a/crates/molc/src/lib.rs +++ /dev/null @@ -1,466 +0,0 @@ -pub mod messages; - -use std::fs::File; -use std::io::{stdout, Read, Write}; -use std::path::Path; -use std::{collections::HashMap, sync::mpsc::Sender}; - -use lsp_types::{ - CompletionContext, CompletionParams, CompletionResponse, CompletionTriggerKind, - DidChangeTextDocumentParams, DidOpenTextDocumentParams, DocumentSymbolParams, - DocumentSymbolResponse, FoldingRange, FoldingRangeParams, GotoDefinitionParams, - GotoDefinitionResponse, Hover, HoverParams, InitializeResult, Location, Position, Range, - ReferenceContext, ReferenceParams, RenameParams, ServerCapabilities, SignatureHelp, - SignatureHelpContext, SignatureHelpParams, SignatureHelpTriggerKind, - TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem, - TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, WorkspaceEdit, -}; -use serde::de::Deserialize; -use serde::Serialize; -use serde_json::{json, Value}; - -use crate::messages::{ErrorMessage, LogMessage, ShowMessage}; - -fn safe_yield() { - std::thread::yield_now(); - std::thread::sleep(std::time::Duration::from_millis(10)); -} - -pub fn add_char(line: u32, character: u32, text: &str) -> TextDocumentContentChangeEvent { - TextDocumentContentChangeEvent { - range: Some(Range { - start: Position { line, character }, - end: Position { line, character }, - }), - range_length: None, - text: text.to_string(), - } -} - -pub fn abs_pos(uri: Url, line: u32, col: u32) -> TextDocumentPositionParams { - TextDocumentPositionParams { - text_document: TextDocumentIdentifier::new(uri), - position: Position { - line, - character: col, - }, - } -} - -pub fn oneline_range(line: u32, from: u32, to: u32) -> Range { - Range { - start: Position { - line, - character: from, - }, - end: Position { - line, - character: to, - }, - } -} - -pub fn parse_msgs(_input: &str) -> Vec { - let mut input = _input; - let mut msgs = Vec::new(); - loop { - if input.starts_with("Content-Length: ") { - let idx = "Content-Length: ".len(); - input = &input[idx..]; - } else { - break; - } - let dights = input.find("\r\n").unwrap(); - let len = input[..dights].parse::().unwrap(); - let idx = dights + "\r\n\r\n".len(); - input = &input[idx..]; - let msg = &input - .get(..len) - .unwrap_or_else(|| panic!("len: {len}, input: `{input}` -> _input: `{_input}`")); - input = &input[len..]; - msgs.push(serde_json::from_str(msg).unwrap()); - } - msgs -} - -pub type Result = std::result::Result>; - -pub trait RedirectableStdout { - fn sender(&self) -> Option<&Sender>; - - fn send_stdout(&self, message: &T) -> Result<()> { - if let Some(sender) = self.sender() { - sender.send(serde_json::to_value(message)?)?; - } else { - let msg = serde_json::to_string(message)?; - let mut stdout = stdout().lock(); - write!(stdout, "Content-Length: {}\r\n\r\n{}", msg.len(), msg)?; - stdout.flush()?; - } - Ok(()) - } - - fn send_log>(&self, msg: S) -> Result<()> { - if cfg!(debug_assertions) || cfg!(feature = "debug") { - self.send_stdout(&LogMessage::new(msg)) - } else { - Ok(()) - } - } - - #[allow(unused)] - fn send_info>(&self, msg: S) -> Result<()> { - self.send_stdout(&ShowMessage::info(msg)) - } - - fn send_error_info>(&self, msg: S) -> Result<()> { - self.send_stdout(&ShowMessage::error(msg)) - } - - fn send_error>(&self, id: Option, code: i64, msg: S) -> Result<()> { - self.send_stdout(&ErrorMessage::new( - id, - json!({ "code": code, "message": msg.into() }), - )) - } - - fn send_invalid_req_error(&self) -> Result<()> { - self.send_error(None, -32601, "received an invalid request") - } -} - -pub trait LangServer { - fn dispatch(&mut self, msg: impl Into) -> Result<()>; -} - -pub struct FakeClient { - server: LS, - receiver: std::sync::mpsc::Receiver, - server_capas: Option, - pub responses: Vec, - #[allow(clippy::complexity)] - request_handlers: HashMap Result<()>>>, - ver: i32, - req_id: i64, -} - -impl FakeClient { - /// The server should send responses to the channel at least during testing. - pub fn new(server: LS, receiver: std::sync::mpsc::Receiver) -> Self { - FakeClient { - receiver, - responses: Vec::new(), - ver: 0, - req_id: 0, - server_capas: None, - request_handlers: HashMap::new(), - server, - } - } - - pub fn add_request_handler( - &mut self, - method_name: impl Into, - handler: impl Fn(&Value, &mut LS) -> Result<()> + 'static, - ) { - self.request_handlers - .insert(method_name.into(), Box::new(handler)); - } - - pub fn remove_request_handler(&mut self, method_name: &str) { - self.request_handlers.remove(method_name); - } - - /// Waits for `n` messages to be received. - /// When a request is received, the registered handler will be executed. - pub fn wait_messages(&mut self, n: usize) -> Result<()> { - for _ in 0..n { - if let Ok(msg) = self.receiver.recv() { - if msg.get("method").is_some_and(|_| msg.get("id").is_some()) { - self.handle_server_request(&msg); - } - self.responses.push(msg); - } - } - Ok(()) - } - - /// Waits for a response to the request, where its `id` is expected to be that of `req_id`, - /// and `req_id` will be incremented if the response is successfully received. - /// When a request is received, the registered handler will be executed. - fn wait_for(&mut self) -> Result - where - R: Deserialize<'static>, - { - loop { - if let Ok(msg) = self.receiver.recv() { - if msg.get("method").is_some_and(|_| msg.get("id").is_some()) { - self.handle_server_request(&msg); - } - self.responses.push(msg); - let msg = self.responses.last().unwrap(); - if msg.get("id").is_some_and(|val| val == self.req_id) { - if let Some(result) = msg - .get("result") - .cloned() - .and_then(|res| R::deserialize(res).ok()) - { - self.req_id += 1; - return Ok(result); - } - } - } - safe_yield(); - } - } - - fn handle_server_request(&mut self, msg: &Value) { - if let Some(method) = msg.get("method").and_then(|val| val.as_str()) { - if let Some(handler) = self.request_handlers.get(method) { - if let Err(err) = handler(msg, &mut self.server) { - eprintln!("error: {:?}", err); - } - } - } - } - - /// This will set the server capabilities - pub fn request_initialize(&mut self) -> Result { - let msg = json!({ - "jsonrpc": "2.0", - "id": self.req_id, - "method": "initialize", - }); - self.server.dispatch(msg)?; - let res = self.wait_for::()?; - self.server_capas = Some(res.capabilities.clone()); - Ok(res) - } - - pub fn notify_open(&mut self, file: &str) -> Result<()> { - let uri = Url::from_file_path(Path::new(file).canonicalize().unwrap()).unwrap(); - let mut text = String::new(); - File::open(file).unwrap().read_to_string(&mut text)?; - let params = DidOpenTextDocumentParams { - text_document: TextDocumentItem::new(uri, "erg".to_string(), self.ver, text), - }; - self.ver += 1; - let msg = json!({ - "jsonrpc": "2.0", - "method": "textDocument/didOpen", - "params": params, - }); - self.server.dispatch(msg)?; - Ok(()) - } - - pub fn notify_change( - &mut self, - uri: Url, - change: TextDocumentContentChangeEvent, - ) -> Result<()> { - let params = DidChangeTextDocumentParams { - text_document: VersionedTextDocumentIdentifier::new(uri.clone(), self.ver), - content_changes: vec![change], - }; - self.ver += 1; - let msg = json!({ - "jsonrpc": "2.0", - "method": "textDocument/didChange", - "params": params, - }); - self.server.dispatch(msg)?; - Ok(()) - } - - fn is_trigger_char(&self, character: &str) -> bool { - self.server_capas.as_ref().is_some_and(|cap| { - cap.completion_provider.as_ref().is_some_and(|comp| { - comp.trigger_characters - .as_ref() - .is_some_and(|chars| chars.iter().any(|c| c == character)) - }) - }) - } - - pub fn request_completion( - &mut self, - uri: Url, - line: u32, - col: u32, - character: &str, - ) -> Result> { - let text_document_position = abs_pos(uri, line, col); - let trigger_kind = if self.is_trigger_char(character) { - CompletionTriggerKind::TRIGGER_CHARACTER - } else { - CompletionTriggerKind::INVOKED - }; - let trigger_character = self - .is_trigger_char(character) - .then_some(character.to_string()); - let context = Some(CompletionContext { - trigger_kind, - trigger_character, - }); - let params = CompletionParams { - text_document_position, - context, - work_done_progress_params: Default::default(), - partial_result_params: Default::default(), - }; - let msg = json!({ - "jsonrpc": "2.0", - "id": self.req_id, - "method": "textDocument/completion", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::>() - } - - pub fn request_rename( - &mut self, - uri: Url, - line: u32, - col: u32, - new_name: &str, - ) -> Result> { - let text_document_position = abs_pos(uri, line, col); - let params = RenameParams { - text_document_position, - new_name: new_name.to_string(), - work_done_progress_params: Default::default(), - }; - let msg = json!({ - "jsonrpc": "2.0", - "id": self.req_id, - "method": "textDocument/rename", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::>() - } - - pub fn request_signature_help( - &mut self, - uri: Url, - line: u32, - col: u32, - character: &str, - ) -> Result> { - let text_document_position_params = abs_pos(uri, line, col); - let context = SignatureHelpContext { - trigger_kind: SignatureHelpTriggerKind::TRIGGER_CHARACTER, - trigger_character: Some(character.to_string()), - is_retrigger: false, - active_signature_help: None, - }; - let params = SignatureHelpParams { - text_document_position_params, - context: Some(context), - work_done_progress_params: Default::default(), - }; - let msg = json!({ - "jsonrpc": "2.0", - "id": self.req_id, - "method": "textDocument/signatureHelp", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::>() - } - - pub fn request_hover(&mut self, uri: Url, line: u32, col: u32) -> Result> { - let params = HoverParams { - text_document_position_params: abs_pos(uri, line, col), - work_done_progress_params: Default::default(), - }; - let msg = json!({ - "jsonrpc": "2.0", - "id": self.req_id, - "method": "textDocument/hover", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::>() - } - - pub fn request_references( - &mut self, - uri: Url, - line: u32, - col: u32, - ) -> Result>> { - let context = ReferenceContext { - include_declaration: false, - }; - let params = ReferenceParams { - text_document_position: abs_pos(uri, line, col), - context, - work_done_progress_params: Default::default(), - partial_result_params: Default::default(), - }; - let msg = json!({ - "jsonrpc": "2.0", - "id": self.req_id, - "method": "textDocument/references", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::>>() - } - - pub fn request_goto_definition( - &mut self, - uri: Url, - line: u32, - col: u32, - ) -> Result> { - let params = GotoDefinitionParams { - text_document_position_params: abs_pos(uri, line, col), - work_done_progress_params: Default::default(), - partial_result_params: Default::default(), - }; - let msg = json!({ - "jsonrpc": "2.0", - "id": self.req_id, - "method": "textDocument/definition", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::>() - } - - pub fn request_folding_range(&mut self, uri: Url) -> Result>> { - let params = FoldingRangeParams { - text_document: TextDocumentIdentifier::new(uri), - work_done_progress_params: Default::default(), - partial_result_params: Default::default(), - }; - let msg = json!({ - "jsonrpc": "2.0", - "id": self.req_id, - "method": "textDocument/foldingRange", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::>>() - } - - pub fn request_document_symbols(&mut self, uri: Url) -> Result> { - let params = DocumentSymbolParams { - text_document: TextDocumentIdentifier::new(uri), - work_done_progress_params: Default::default(), - partial_result_params: Default::default(), - }; - let msg = json!({ - "jsonrpc": "2.0", - "id": self.req_id, - "method": "textDocument/documentSymbol", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::>() - } -} diff --git a/crates/molc/src/messages.rs b/crates/molc/src/messages.rs deleted file mode 100644 index 8dc77c28..00000000 --- a/crates/molc/src/messages.rs +++ /dev/null @@ -1,79 +0,0 @@ -use serde::{Deserialize, Serialize}; -use serde_json::json; -use serde_json::{Number, Value}; - -#[derive(Debug, Serialize, Deserialize)] -pub struct ErrorMessage { - jsonrpc: String, - id: Option, - error: Value, -} - -impl ErrorMessage { - #[allow(dead_code)] - pub fn new>(id: Option, error: Value) -> Self { - Self { - jsonrpc: "2.0".into(), - id: id.map(|i| i.into()), - error, - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct LogMessage { - jsonrpc: String, - method: String, - params: Value, -} - -impl LogMessage { - pub fn new>(message: S) -> Self { - Self { - jsonrpc: "2.0".into(), - method: "window/logMessage".into(), - params: json! { - { - "type": 3, - "message": message.into(), - } - }, - } - } -} - -#[derive(Debug, Serialize, Deserialize)] -pub struct ShowMessage { - jsonrpc: String, - method: String, - params: Value, -} - -impl ShowMessage { - #[allow(unused)] - pub fn info>(message: S) -> Self { - Self { - jsonrpc: "2.0".into(), - method: "window/showMessage".into(), - params: json! { - { - "type": 3, - "message": message.into(), - } - }, - } - } - - pub fn error>(message: S) -> Self { - Self { - jsonrpc: "2.0".into(), - method: "window/showMessage".into(), - params: json! { - { - "type": 1, - "message": message.into(), - } - }, - } - } -}