diff --git a/Cargo.lock b/Cargo.lock index dcc020234..12b2910b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -55,12 +55,6 @@ version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" -[[package]] -name = "bitflags" -version = "2.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635" - [[package]] name = "cc" version = "1.0.83" @@ -82,7 +76,7 @@ version = "0.25.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" dependencies = [ - "bitflags 1.3.2", + "bitflags", "crossterm_winapi", "libc", "mio", @@ -107,8 +101,8 @@ version = "0.1.32-nightly.2" dependencies = [ "erg_common", "erg_compiler", - "gag", "lsp-types", + "molc", "serde", "serde_json", ] @@ -149,44 +143,6 @@ dependencies = [ "unicode-xid", ] -[[package]] -name = "errno" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "136526188508e25c6fef639d7927dfb3e0e3084488bf202267829cf7fc23dbdd" -dependencies = [ - "errno-dragonfly", - "libc", - "windows-sys", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "fastrand" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6999dc1837253364c2ebb0704ba97994bd874e8f195d665c50b7548f6ea92764" - -[[package]] -name = "filedescriptor" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7199d965852c3bac31f779ef99cbb4537f80e952e2d6aa0ffeb30cce00f4f46e" -dependencies = [ - "libc", - "thiserror", - "winapi", -] - [[package]] name = "form_urlencoded" version = "1.2.0" @@ -196,16 +152,6 @@ dependencies = [ "percent-encoding", ] -[[package]] -name = "gag" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a713bee13966e9fbffdf7193af71d54a6b35a0bb34997cd6c9519ebeb5005972" -dependencies = [ - "filedescriptor", - "tempfile", -] - [[package]] name = "gimli" version = "0.28.0" @@ -234,12 +180,6 @@ version = "0.2.147" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" -[[package]] -name = "linux-raw-sys" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503" - [[package]] name = "lock_api" version = "0.4.10" @@ -262,7 +202,7 @@ version = "0.93.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9be6e9c7e2d18f651974370d7aff703f9513e0df6e464fd795660edc77e6ca51" dependencies = [ - "bitflags 1.3.2", + "bitflags", "serde", "serde_json", "serde_repr", @@ -305,13 +245,24 @@ dependencies = [ "windows-sys", ] +[[package]] +name = "molc" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a19b669aab31ca7552fc43cb9ab08e325113aa090f7bf97a2112b3d6241ba898" +dependencies = [ + "lsp-types", + "serde", + "serde_json", +] + [[package]] name = "nix" version = "0.23.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" dependencies = [ - "bitflags 1.3.2", + "bitflags", "cc", "cfg-if", "libc", @@ -386,7 +337,7 @@ version = "0.3.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" dependencies = [ - "bitflags 1.3.2", + "bitflags", ] [[package]] @@ -395,19 +346,6 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" -[[package]] -name = "rustix" -version = "0.38.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0c3dde1fc030af041adc40e79c0e7fbcf431dd24870053d187d7c66e4b87453" -dependencies = [ - "bitflags 2.4.0", - "errno", - "libc", - "linux-raw-sys", - "windows-sys", -] - [[package]] name = "ryu" version = "1.0.15" @@ -509,39 +447,6 @@ dependencies = [ "unicode-ident", ] -[[package]] -name = "tempfile" -version = "3.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cb94d2f3cc536af71caac6b6fcebf65860b347e7ce0cc9ebe8f70d3e521054ef" -dependencies = [ - "cfg-if", - "fastrand", - "redox_syscall", - "rustix", - "windows-sys", -] - -[[package]] -name = "thiserror" -version = "1.0.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9d6d7a740b8a666a7e828dd00da9c0dc290dff53154ea77ac109281de90589b7" -dependencies = [ - "thiserror-impl", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.48" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49922ecae66cc8a249b77e68d1d0623c1b2c514f0060c27cdc68bd62a1219d35" -dependencies = [ - "proc-macro2", - "quote", - "syn", -] - [[package]] name = "thread_local" version = "1.1.7" diff --git a/crates/els/Cargo.toml b/crates/els/Cargo.toml index 9a9aba4a0..a38bef929 100644 --- a/crates/els/Cargo.toml +++ b/crates/els/Cargo.toml @@ -23,13 +23,11 @@ experimental = ["erg_common/experimental", "erg_compiler/experimental"] [dependencies] erg_common = { workspace = true, features = ["els"] } erg_compiler = { workspace = true, features = ["els"] } +molc = { version = "0.1.0" } serde = { version = "1.0", features = ["derive"] } serde_json = "1.0.85" lsp-types = { version = "0.93.2", features = ["proposed"] } -[dev-dependencies] -gag = "1" - [lib] path = "lib.rs" diff --git a/crates/els/call_hierarchy.rs b/crates/els/call_hierarchy.rs index cc8e3d93a..88751b6f1 100644 --- a/crates/els/call_hierarchy.rs +++ b/crates/els/call_hierarchy.rs @@ -11,7 +11,7 @@ use lsp_types::{ }; use crate::_log; -use crate::server::{ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::symbol::symbol_kind; use crate::util::{abs_loc_to_lsp_loc, loc_to_pos, NormalizedUrl}; @@ -35,7 +35,7 @@ impl Server { params: CallHierarchyIncomingCallsParams, ) -> ELSResult>> { let mut res = vec![]; - _log!("call hierarchy incoming calls requested: {params:?}"); + _log!(self, "call hierarchy incoming calls requested: {params:?}"); let Some(data) = params.item.data.as_ref().and_then(|d| d.as_str()) else { return Ok(None); }; @@ -80,7 +80,7 @@ impl Server { &mut self, params: CallHierarchyOutgoingCallsParams, ) -> ELSResult>> { - _log!("call hierarchy outgoing calls requested: {params:?}"); + _log!(self, "call hierarchy outgoing calls requested: {params:?}"); Ok(None) } @@ -88,7 +88,7 @@ impl Server { &mut self, params: CallHierarchyPrepareParams, ) -> ELSResult>> { - _log!("call hierarchy prepare requested: {params:?}"); + _log!(self, "call hierarchy prepare requested: {params:?}"); let mut res = vec![]; let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri); let pos = params.text_document_position_params.position; diff --git a/crates/els/code_action.rs b/crates/els/code_action.rs index 440b00333..01990c86f 100644 --- a/crates/els/code_action.rs +++ b/crates/els/code_action.rs @@ -14,7 +14,7 @@ use lsp_types::{ Position, Range, TextEdit, Url, WorkspaceEdit, }; -use crate::server::{send_log, ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::{self, NormalizedUrl}; impl Server { @@ -29,11 +29,11 @@ impl Server { }; let mut map = HashMap::new(); let Some(visitor) = self.get_visitor(&uri) else { - send_log("visitor not found")?; + self.send_log("visitor not found")?; return Ok(None); }; let Some(result) = self.analysis_result.get(&uri) else { - send_log("artifact not found")?; + self.send_log("artifact not found")?; return Ok(None); }; let warns = result @@ -50,7 +50,7 @@ impl Server { match visitor.get_min_expr(pos) { Some(Expr::Def(def)) => { let Some(mut range) = util::loc_to_range(def.loc()) else { - send_log("range not found")?; + self.send_log("range not found")?; continue; }; let next = lsp_types::Range { @@ -72,7 +72,7 @@ impl Server { } Some(";") => range.end.character += 1, Some(other) => { - send_log(format!("? {other}"))?; + self.send_log(format!("? {other}"))?; } } let edit = TextEdit::new(range, "".to_string()); @@ -228,7 +228,7 @@ impl Server { &mut self, params: CodeActionParams, ) -> ELSResult> { - send_log(format!("code action requested: {params:?}"))?; + self.send_log(format!("code action requested: {params:?}"))?; let result = match params .context .only @@ -238,7 +238,7 @@ impl Server { Some("quickfix") => self.send_quick_fix(¶ms)?, None => self.send_normal_action(¶ms)?, Some(other) => { - send_log(&format!("Unknown code action requested: {other}"))?; + self.send_log(&format!("Unknown code action requested: {other}"))?; vec![] } }; @@ -254,7 +254,7 @@ impl Server { &mut self, action: CodeAction, ) -> ELSResult { - send_log(format!("code action resolve requested: {action:?}"))?; + self.send_log(format!("code action resolve requested: {action:?}"))?; match &action.title[..] { "Extract into function" | "Extract into variable" => { self.resolve_extract_action(action) diff --git a/crates/els/code_lens.rs b/crates/els/code_lens.rs index de012bea8..35f8cbdfe 100644 --- a/crates/els/code_lens.rs +++ b/crates/els/code_lens.rs @@ -4,7 +4,7 @@ use erg_compiler::hir::Expr; use lsp_types::{CodeLens, CodeLensParams}; -use crate::server::{send_log, ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::{self, NormalizedUrl}; impl Server { @@ -12,7 +12,7 @@ impl Server { &mut self, params: CodeLensParams, ) -> ELSResult>> { - send_log("code lens requested")?; + self.send_log("code lens requested")?; let uri = NormalizedUrl::new(params.text_document.uri); // TODO: parallelize let result = [ diff --git a/crates/els/command.rs b/crates/els/command.rs index 1a3b9cba2..e2d1ff55c 100644 --- a/crates/els/command.rs +++ b/crates/els/command.rs @@ -8,7 +8,7 @@ use erg_compiler::hir::Expr; use lsp_types::{Command, ExecuteCommandParams, Location, Url}; use crate::_log; -use crate::server::{ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::{self, NormalizedUrl}; impl Server { @@ -16,11 +16,11 @@ impl Server { &mut self, params: ExecuteCommandParams, ) -> ELSResult> { - _log!("command requested: {}", params.command); + _log!(self, "command requested: {}", params.command); #[allow(clippy::match_single_binding)] match ¶ms.command[..] { other => { - _log!("unknown command {other}: {params:?}"); + _log!(self, "unknown command {other}: {params:?}"); Ok(None) } } diff --git a/crates/els/completion.rs b/crates/els/completion.rs index 8c450d570..198f271e5 100644 --- a/crates/els/completion.rs +++ b/crates/els/completion.rs @@ -31,7 +31,7 @@ use lsp_types::{ MarkupContent, MarkupKind, Position, Range, TextEdit, }; -use crate::server::{send_log, ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::{self, NormalizedUrl}; fn comp_item_kind(vi: &VarInfo) -> CompletionItemKind { @@ -355,7 +355,7 @@ impl CompletionCache { let clone = cache.clone(); spawn_new_thread( move || { - crate::_log!("load_modules"); + // crate::_log!("load_modules"); let major_mods = [ "argparse", "array", @@ -490,7 +490,7 @@ impl Server { &mut self, params: CompletionParams, ) -> ELSResult> { - send_log(format!("completion requested: {params:?}"))?; + self.send_log(format!("completion requested: {params:?}"))?; let uri = NormalizedUrl::new(params.text_document_position.text_document.uri); let path = util::uri_to_path(&uri); let pos = params.text_document_position.position; @@ -514,7 +514,7 @@ impl Server { Some("(") => CompletionKind::LParen, _ => CompletionKind::Local, }; - send_log(format!("CompletionKind: {comp_kind:?}"))?; + self.send_log(format!("CompletionKind: {comp_kind:?}"))?; let mut result: Vec = vec![]; let mut already_appeared = Set::new(); let contexts = if comp_kind.should_be_local() { @@ -629,7 +629,7 @@ impl Server { } result.extend(self.neighbor_completion(&uri, arg_pt, &mut already_appeared)); } - send_log(format!("completion items: {}", result.len()))?; + self.send_log(format!("completion items: {}", result.len()))?; Ok(Some(CompletionResponse::Array(result))) } @@ -637,7 +637,7 @@ impl Server { &mut self, mut item: CompletionItem, ) -> ELSResult { - send_log(format!("completion resolve requested: {item:?}"))?; + self.send_log(format!("completion resolve requested: {item:?}"))?; if let Some(data) = &item.data { let mut contents = vec![]; let Ok(def_loc) = data.as_str().unwrap_or_default().parse::() else { diff --git a/crates/els/definition.rs b/crates/els/definition.rs index 74d03138f..bf906a334 100644 --- a/crates/els/definition.rs +++ b/crates/els/definition.rs @@ -10,7 +10,7 @@ use erg_compiler::varinfo::VarInfo; use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse, Location, Position, Url}; -use crate::server::{send_log, ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::{self, NormalizedUrl}; impl Server { @@ -20,12 +20,12 @@ impl Server { token: &Token, ) -> ELSResult> { if !token.category_is(TokenCategory::Symbol) { - send_log(format!("not symbol: {token}"))?; + self.send_log(format!("not symbol: {token}"))?; Ok(None) } else if let Some(visitor) = self.get_visitor(uri) { Ok(visitor.get_info(token)) } else { - send_log("not found")?; + self.send_log("not found")?; Ok(None) } } @@ -99,7 +99,7 @@ impl Server { return Ok(Some(lsp_types::Location::new(def_uri, range))); } _ => { - send_log("not found (maybe builtin)")?; + self.send_log("not found (maybe builtin)")?; return Ok(None); } } @@ -112,7 +112,7 @@ impl Server { Ok(Some(lsp_types::Location::new(def_uri, range))) } _ => { - send_log("not found (maybe builtin)")?; + self.send_log("not found (maybe builtin)")?; Ok(None) } } @@ -120,7 +120,7 @@ impl Server { Ok(None) } } else { - send_log("lex error occurred")?; + self.send_log("lex error occurred")?; Ok(None) } } @@ -129,7 +129,7 @@ impl Server { &mut self, params: GotoDefinitionParams, ) -> ELSResult> { - send_log(format!("definition requested: {params:?}"))?; + self.send_log(format!("definition requested: {params:?}"))?; let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri); let pos = params.text_document_position_params.position; let result = self.get_definition_location(&uri, pos)?; diff --git a/crates/els/diagnostics.rs b/crates/els/diagnostics.rs index 3946dcc9c..eefc934f1 100644 --- a/crates/els/diagnostics.rs +++ b/crates/els/diagnostics.rs @@ -23,7 +23,7 @@ use crate::_log; use crate::channels::WorkerMessage; use crate::diff::{ASTDiff, HIRDiff}; use crate::server::{ - send, send_log, AnalysisResult, DefaultFeatures, ELSResult, Server, ASK_AUTO_SAVE_ID, + AnalysisResult, DefaultFeatures, ELSResult, RedirectableStdout, Server, ASK_AUTO_SAVE_ID, HEALTH_CHECKER_ID, }; use crate::util::{self, NormalizedUrl}; @@ -39,7 +39,7 @@ impl Server { uri: NormalizedUrl, code: S, ) -> ELSResult<()> { - send_log(format!("checking {uri}"))?; + self.send_log(format!("checking {uri}"))?; let path = util::uri_to_path(&uri); let mode = if path.to_string_lossy().ends_with(".d.er") { "declare" @@ -48,14 +48,14 @@ impl Server { }; if let Some((old, new)) = self.analysis_result.get_ast(&uri).zip(self.get_ast(&uri)) { if ASTDiff::diff(old, &new).is_nop() { - crate::_log!("no changes: {uri}"); + crate::_log!(self, "no changes: {uri}"); return Ok(()); } } let mut checker = self.get_checker(path.clone()); let artifact = match checker.build(code.into(), mode) { Ok(artifact) => { - send_log(format!( + self.send_log(format!( "checking {uri} passed, found warns: {}", artifact.warns.len() ))?; @@ -63,14 +63,14 @@ impl Server { // clear previous diagnostics self.send_diagnostics(uri.clone().raw(), vec![])?; for (uri, diags) in uri_and_diags.into_iter() { - send_log(format!("{uri}, warns: {}", diags.len()))?; + self.send_log(format!("{uri}, warns: {}", diags.len()))?; self.send_diagnostics(uri, diags)?; } artifact.into() } Err(artifact) => { - send_log(format!("found errors: {}", artifact.errors.len()))?; - send_log(format!("found warns: {}", artifact.warns.len()))?; + self.send_log(format!("found errors: {}", artifact.errors.len()))?; + self.send_log(format!("found warns: {}", artifact.warns.len()))?; let diags = artifact .errors .clone() @@ -82,7 +82,7 @@ impl Server { self.send_diagnostics(uri.clone().raw(), vec![])?; } for (uri, diags) in uri_and_diags.into_iter() { - send_log(format!("{uri}, errs & warns: {}", diags.len()))?; + self.send_log(format!("{uri}, errs & warns: {}", diags.len()))?; self.send_diagnostics(uri, diags)?; } artifact @@ -108,12 +108,12 @@ impl Server { .insert(uri.clone(), AnalysisResult::new(module, artifact)); } if let Some(module) = checker.pop_context() { - send_log(format!("{uri}: {}", module.context.name))?; + self.send_log(format!("{uri}: {}", module.context.name))?; self.modules.insert(uri.clone(), module); } let dependents = self.dependents_of(&uri); for dep in dependents { - // _log!("dep: {dep}"); + // _log!(self, "dep: {dep}"); let code = self.file_cache.get_entire_code(&dep)?.to_string(); self.check_file(dep, code)?; } @@ -122,19 +122,19 @@ impl Server { pub(crate) fn quick_check_file(&mut self, uri: NormalizedUrl) -> ELSResult<()> { let Some(old) = self.analysis_result.get_ast(&uri) else { - crate::_log!("not found"); + crate::_log!(self, "not found"); return Ok(()); }; let Some(new) = self.get_ast(&uri) else { - crate::_log!("not found"); + crate::_log!(self, "not found"); return Ok(()); }; let ast_diff = ASTDiff::diff(old, &new); - crate::_log!("diff: {ast_diff}"); + crate::_log!(self, "diff: {ast_diff}"); if let Some(mut lowerer) = self.steal_lowerer(&uri) { let hir = self.analysis_result.get_mut_hir(&uri); if let Some((hir_diff, hir)) = HIRDiff::new(ast_diff, &mut lowerer).zip(hir) { - crate::_log!("hir_diff: {hir_diff}"); + crate::_log!(self, "hir_diff: {hir_diff}"); hir_diff.update(hir); } self.restore_lowerer(uri, lowerer); @@ -154,7 +154,7 @@ impl Server { .unwrap_or(err.input.path().to_path_buf()), ); let Ok(err_uri) = res_uri else { - crate::_log!("failed to get uri: {}", err.input.path().display()); + crate::_log!(self, "failed to get uri: {}", err.input.path().display()); continue; }; let mut message = remove_style(&err.core.main_message); @@ -214,13 +214,13 @@ impl Server { .map(|doc| doc.publish_diagnostics.is_some()) .unwrap_or(false) { - send(&json!({ + self.send_stdout(&json!({ "jsonrpc": "2.0", "method": "textDocument/publishDiagnostics", "params": params, }))?; } else { - send_log("the client does not support diagnostics")?; + self.send_log("the client does not support diagnostics")?; } Ok(()) } @@ -242,7 +242,7 @@ impl Server { == Some("afterDelay") }) { - _log!("Auto saving is enabled"); + _log!(_self, "Auto saving is enabled"); break; } for uri in _self.file_cache.entries() { @@ -272,20 +272,25 @@ impl Server { pub fn start_client_health_checker(&self, receiver: Receiver>) { const INTERVAL: Duration = Duration::from_secs(5); const TIMEOUT: Duration = Duration::from_secs(10); + if self.stdout_redirect.is_some() { + return; + } + let _self = self.clone(); // let mut self_ = self.clone(); // FIXME: close this thread when the server is restarted spawn_new_thread( move || { loop { - // send_log("checking client health").unwrap(); + // self.send_log("checking client health").unwrap(); let params = ConfigurationParams { items: vec![] }; - send(&json!({ - "jsonrpc": "2.0", - "id": HEALTH_CHECKER_ID, - "method": "workspace/configuration", - "params": params, - })) - .unwrap(); + _self + .send_stdout(&json!({ + "jsonrpc": "2.0", + "id": HEALTH_CHECKER_ID, + "method": "workspace/configuration", + "params": params, + })) + .unwrap(); sleep(INTERVAL); } }, @@ -299,12 +304,12 @@ impl Server { break; } Ok(_) => { - // send_log("client health check passed").unwrap(); + // self.send_log("client health check passed").unwrap(); } Err(_) => { lsp_log!("Client health check timed out"); - // lsp_log!("Restart the server"); - // _log!("Restart the server"); + // lsp_log!(self, "Restart the server"); + // _log!(self, "Restart the server"); // send_error_info("Something went wrong, ELS has been restarted").unwrap(); // self_.restart(); panic!("Client health check timed out"); diff --git a/crates/els/diff.rs b/crates/els/diff.rs index 5cd73a7a9..6096e315e 100644 --- a/crates/els/diff.rs +++ b/crates/els/diff.rs @@ -95,8 +95,8 @@ impl HIRDiff { ASTDiff::Addition(idx, expr) => { let expr = lowerer .lower_chunk(expr, None) - .map_err(|err| { - crate::_log!("err: {err}"); + .map_err(|_err| { + // crate::_log!(self, "err: {err}"); }) .ok()?; Some(Self::Addition(idx, expr)) @@ -112,8 +112,8 @@ impl HIRDiff { } let expr = lowerer .lower_chunk(expr, None) - .map_err(|err| { - crate::_log!("err: {err}"); + .map_err(|_err| { + // crate::_log!(self, "err: {err}"); }) .ok()?; Some(Self::Modification(idx, expr)) diff --git a/crates/els/file_cache.rs b/crates/els/file_cache.rs index 74050582d..15f0fd346 100644 --- a/crates/els/file_cache.rs +++ b/crates/els/file_cache.rs @@ -1,5 +1,6 @@ use std::fs::File; use std::io::Read; +use std::sync::mpsc::Sender; use lsp_types::{ DidChangeTextDocumentParams, FileOperationFilter, FileOperationPattern, @@ -8,6 +9,7 @@ use lsp_types::{ TextDocumentSyncKind, TextDocumentSyncOptions, Url, WorkspaceFileOperationsServerCapabilities, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, }; +use serde_json::Value; use erg_common::dict::Dict; use erg_common::shared::Shared; @@ -16,7 +18,7 @@ use erg_compiler::erg_parser::lex::Lexer; use erg_compiler::erg_parser::token::{Token, TokenCategory, TokenStream}; use crate::_log; -use crate::server::ELSResult; +use crate::server::{ELSResult, RedirectableStdout}; use crate::util::{self, NormalizedUrl}; fn _get_code_from_uri(uri: &Url) -> ELSResult { @@ -48,12 +50,20 @@ impl FileCacheEntry { /// This struct can save changes in real-time & incrementally. #[derive(Debug, Clone, Default)] pub struct FileCache { + stdout_redirect: Option>, pub files: Shared>, } +impl RedirectableStdout for FileCache { + fn sender(&self) -> Option<&Sender> { + self.stdout_redirect.as_ref() + } +} + impl FileCache { - pub fn new() -> Self { + pub fn new(stdout_redirect: Option>) -> Self { Self { + stdout_redirect, files: Shared::new(Dict::new()), } } @@ -230,7 +240,7 @@ impl FileCache { let entry = ent.get(uri); if let Some(entry) = entry { if ver.map_or(false, |ver| ver <= entry.ver) { - // crate::_log!("171: double update detected: {ver:?}, {}, code:\n{}", entry.ver, entry.code); + // crate::_log!(self, "171: double update detected: {ver:?}, {}, code:\n{}", entry.ver, entry.code); return; } } @@ -275,7 +285,13 @@ impl FileCache { return; }; if entry.ver >= params.text_document.version { - // crate::_log!("212: double update detected {}, {}, code:\n{}", entry.ver, params.text_document.version, entry.code); + crate::_log!( + self, + "212: double update detected {}, {}, code:\n{}", + entry.ver, + params.text_document.version, + entry.code + ); return; } let mut code = entry.code.clone(); @@ -301,15 +317,15 @@ impl FileCache { pub fn rename_files(&mut self, params: &RenameFilesParams) -> ELSResult<()> { for file in ¶ms.files { let Ok(old_uri) = NormalizedUrl::parse(&file.old_uri) else { - _log!("failed to parse old uri: {}", file.old_uri); + _log!(self, "failed to parse old uri: {}", file.old_uri); continue; }; let Ok(new_uri) = NormalizedUrl::parse(&file.new_uri) else { - _log!("failed to parse new uri: {}", file.new_uri); + _log!(self, "failed to parse new uri: {}", file.new_uri); continue; }; let Some(entry) = self.files.borrow_mut().remove(&old_uri) else { - _log!("failed to find old uri: {}", file.old_uri); + _log!(self, "failed to find old uri: {}", file.old_uri); continue; }; self.files.borrow_mut().insert(new_uri, entry); diff --git a/crates/els/folding_range.rs b/crates/els/folding_range.rs index f65995d31..8fed9d562 100644 --- a/crates/els/folding_range.rs +++ b/crates/els/folding_range.rs @@ -7,7 +7,7 @@ use erg_compiler::erg_parser::parse::Parsable; use lsp_types::{FoldingRange, FoldingRangeKind, FoldingRangeParams}; use crate::_log; -use crate::server::{ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::NormalizedUrl; fn imports_range(start: &Location, end: &Location) -> Option { @@ -25,7 +25,7 @@ impl Server { &mut self, params: FoldingRangeParams, ) -> ELSResult>> { - _log!("folding range requested: {params:?}"); + _log!(self, "folding range requested: {params:?}"); let uri = NormalizedUrl::new(params.text_document.uri); let mut res = vec![]; res.extend(self.fold_imports(&uri)); diff --git a/crates/els/hover.rs b/crates/els/hover.rs index 912c29b1b..d95b75b83 100644 --- a/crates/els/hover.rs +++ b/crates/els/hover.rs @@ -8,7 +8,7 @@ use erg_compiler::varinfo::{AbsLocation, VarInfo}; use lsp_types::{Hover, HoverContents, HoverParams, MarkedString, Url}; -use crate::server::{send_log, ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::{self, NormalizedUrl}; const PROG_LANG: &str = if PYTHON_MODE { "python" } else { "erg" }; @@ -80,7 +80,7 @@ macro_rules! next { impl Server { pub(crate) fn handle_hover(&mut self, params: HoverParams) -> ELSResult> { - send_log(format!("hover requested : {params:?}"))?; + self.send_log(format!("hover requested : {params:?}"))?; let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri); let pos = params.text_document_position_params.position; let mut contents = vec![]; @@ -176,7 +176,7 @@ impl Server { } } } else { - send_log("lex error")?; + self.send_log("lex error")?; } Ok(Some(Hover { contents: HoverContents::Array(sort_hovers(contents)), diff --git a/crates/els/implementation.rs b/crates/els/implementation.rs index 238f23b9d..71568b7c0 100644 --- a/crates/els/implementation.rs +++ b/crates/els/implementation.rs @@ -3,7 +3,7 @@ use erg_compiler::erg_parser::parse::Parsable; use lsp_types::request::{GotoImplementationParams, GotoImplementationResponse}; use crate::_log; -use crate::server::{ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::{loc_to_pos, NormalizedUrl}; impl Server { @@ -11,7 +11,7 @@ impl Server { &mut self, params: GotoImplementationParams, ) -> ELSResult> { - _log!("implementation requested: {params:?}"); + _log!(self, "implementation requested: {params:?}"); let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri); let pos = params.text_document_position_params.position; let Some(symbol) = self.file_cache.get_symbol(&uri, pos) else { diff --git a/crates/els/inlay_hint.rs b/crates/els/inlay_hint.rs index 16e034d17..1651469a3 100644 --- a/crates/els/inlay_hint.rs +++ b/crates/els/inlay_hint.rs @@ -19,7 +19,7 @@ use lsp_types::{ }; use crate::_log; -use crate::server::{send, send_log, ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::abs_loc_to_lsp_loc; use crate::util::{self, loc_to_range, NormalizedUrl}; @@ -294,7 +294,7 @@ impl Server { &mut self, params: InlayHintParams, ) -> ELSResult>> { - send_log(format!("inlay hint request: {params:?}"))?; + self.send_log(format!("inlay hint request: {params:?}"))?; let uri = NormalizedUrl::new(params.text_document.uri); let mut result = vec![]; let gen = InlayHintGenerator { @@ -316,7 +316,7 @@ impl Server { &mut self, mut hint: InlayHint, ) -> ELSResult { - send_log(format!("inlay hint resolve request: {hint:?}"))?; + self.send_log(format!("inlay hint resolve request: {hint:?}"))?; if let Some(data) = &hint.data { let Ok(uri) = data.as_str().unwrap().parse::() else { return Ok(hint); diff --git a/crates/els/main.rs b/crates/els/main.rs index e498dd32c..3f3913b40 100644 --- a/crates/els/main.rs +++ b/crates/els/main.rs @@ -26,6 +26,6 @@ use erg_common::config::ErgConfig; fn main() { let cfg = ErgConfig::default(); - let mut server = server::ErgLanguageServer::new(cfg); + let mut server = server::ErgLanguageServer::new(cfg, None); server.run().unwrap(); } diff --git a/crates/els/message.rs b/crates/els/message.rs index a80394a70..467b3b38d 100644 --- a/crates/els/message.rs +++ b/crates/els/message.rs @@ -1,82 +1,6 @@ -use serde::{Deserialize, Serialize}; -use serde_json::json; -use serde_json::{Number, Value}; +use serde::Serialize; -#[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(), - } - }, - } - } -} +pub use molc::messages::ErrorMessage; #[derive(Serialize)] pub struct LSPResult { diff --git a/crates/els/references.rs b/crates/els/references.rs index c054b21ef..0786aee13 100644 --- a/crates/els/references.rs +++ b/crates/els/references.rs @@ -5,7 +5,7 @@ use erg_compiler::varinfo::AbsLocation; use lsp_types::{Location, Position, ReferenceParams, Url}; use crate::_log; -use crate::server::{ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::{self, NormalizedUrl}; impl Server { @@ -13,7 +13,7 @@ impl Server { &mut self, params: ReferenceParams, ) -> ELSResult>> { - _log!("references: {params:?}"); + _log!(self, "references: {params:?}"); let uri = NormalizedUrl::new(params.text_document_position.text_document.uri); let pos = params.text_document_position.position; let result = self.show_refs_inner(&uri, pos); diff --git a/crates/els/rename.rs b/crates/els/rename.rs index fe8ee22d0..eee764961 100644 --- a/crates/els/rename.rs +++ b/crates/els/rename.rs @@ -22,17 +22,17 @@ use lsp_types::{ WorkspaceEdit, }; -use crate::server::{send, send_error_info, send_log, ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::{self, NormalizedUrl}; impl Server { pub(crate) fn rename(&mut self, msg: &Value) -> ELSResult<()> { let params = RenameParams::deserialize(&msg["params"])?; - send_log(format!("rename request: {params:?}"))?; + self.send_log(format!("rename request: {params:?}"))?; let uri = NormalizedUrl::new(params.text_document_position.text_document.uri); let pos = params.text_document_position.position; if let Some(tok) = self.file_cache.get_symbol(&uri, pos) { - // send_log(format!("token: {tok}"))?; + // self.send_log(format!("token: {tok}"))?; if let Some(vi) = self .get_visitor(&uri) .and_then(|visitor| visitor.get_info(&tok)) @@ -65,14 +65,14 @@ impl Server { _ => format!("this {kind} cannot be renamed"), }; let edit = WorkspaceEdit::new(changes); - send( + self.send_stdout( &json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": edit }), )?; - return send_error_info(error_reason); + return self.send_error_info(error_reason); } Self::commit_change(&mut changes, &vi.def_loc, params.new_name.clone()); if let Some(value) = self.get_index().and_then(|ind| ind.get_refs(&vi.def_loc)) { - // send_log(format!("referrers: {referrers:?}"))?; + // self.send_log(format!("referrers: {referrers:?}"))?; for referrer in value.referrers.iter() { Self::commit_change(&mut changes, referrer, params.new_name.clone()); } @@ -83,11 +83,11 @@ impl Server { } let timestamps = self.get_timestamps(changes.keys()); let edit = WorkspaceEdit::new(changes); - send( + self.send_stdout( &json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": edit }), )?; for _ in 0..20 { - send_log("waiting for file to be modified...")?; + self.send_log("waiting for file to be modified...")?; if self.all_changed(×tamps) { break; } @@ -102,7 +102,9 @@ impl Server { return Ok(()); } } - send(&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": Value::Null })) + self.send_stdout( + &json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": Value::Null }), + ) } fn commit_change( @@ -292,7 +294,7 @@ impl Server { &mut self, params: RenameFilesParams, ) -> ELSResult> { - send_log("workspace/willRenameFiles request")?; + self.send_log("workspace/willRenameFiles request")?; let mut edits = HashMap::new(); let mut renames = vec![]; for file in ¶ms.files { diff --git a/crates/els/semantic.rs b/crates/els/semantic.rs index aed8f1ffd..4a48d648c 100644 --- a/crates/els/semantic.rs +++ b/crates/els/semantic.rs @@ -15,7 +15,7 @@ use lsp_types::{ SemanticToken, SemanticTokenType, SemanticTokens, SemanticTokensParams, SemanticTokensResult, }; -use crate::server::{send_log, ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::{self, NormalizedUrl}; #[derive(Debug)] @@ -288,7 +288,7 @@ impl Server { &mut self, params: SemanticTokensParams, ) -> ELSResult> { - send_log(format!("full semantic tokens request: {params:?}"))?; + self.send_log(format!("full semantic tokens request: {params:?}"))?; let uri = NormalizedUrl::new(params.text_document.uri); let path = util::uri_to_path(&uri); let src = self.file_cache.get_entire_code(&uri)?; diff --git a/crates/els/server.rs b/crates/els/server.rs index 7f9ff9687..24d14eccb 100644 --- a/crates/els/server.rs +++ b/crates/els/server.rs @@ -1,6 +1,6 @@ use std::any::type_name; use std::io; -use std::io::{stdin, stdout, BufRead, Read, Write}; +use std::io::{stdin, BufRead, Read}; use std::ops::Not; use std::path::{Path, PathBuf}; use std::str::FromStr; @@ -26,6 +26,9 @@ use erg_compiler::lower::ASTLowerer; use erg_compiler::module::{SharedCompilerResource, SharedModuleGraph, SharedModuleIndex}; use erg_compiler::ty::HasType; +pub use molc::RedirectableStdout; +use molc::{FakeClient, LangServer}; + use lsp_types::request::{ CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare, CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, @@ -53,7 +56,7 @@ use crate::channels::{SendChannels, Sendable, WorkerMessage}; use crate::completion::CompletionCache; use crate::file_cache::FileCache; use crate::hir_visitor::{ExprKind, HIRVisitor}; -use crate::message::{ErrorMessage, LSPResult, LogMessage, ShowMessage}; +use crate::message::{ErrorMessage, LSPResult}; use crate::util::{self, loc_to_pos, NormalizedUrl}; pub const HEALTH_CHECKER_ID: i64 = 10000; @@ -131,64 +134,12 @@ impl From<&str> for OptionalFeatures { #[macro_export] macro_rules! _log { - ($($arg:tt)*) => { + ($self:ident, $($arg:tt)*) => { let s = format!($($arg)*); - $crate::server::send_log(format!("{}@{}: {s}", file!(), line!())).unwrap(); + $self.send_log(format!("{}@{}: {s}", file!(), line!())).unwrap(); }; } -fn send_stdout(message: &T) -> ELSResult<()> { - 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 read_line() -> io::Result { - let mut line = String::new(); - stdin().lock().read_line(&mut line)?; - Ok(line) -} - -fn read_exact(len: usize) -> io::Result> { - let mut buf = vec![0; len]; - stdin().lock().read_exact(&mut buf)?; - Ok(buf) -} - -pub(crate) fn send(message: &T) -> ELSResult<()> { - send_stdout(message) -} - -pub(crate) fn send_log>(msg: S) -> ELSResult<()> { - if cfg!(debug_assertions) || cfg!(feature = "debug") { - send(&LogMessage::new(msg)) - } else { - Ok(()) - } -} - -#[allow(unused)] -pub(crate) fn send_info>(msg: S) -> ELSResult<()> { - send(&ShowMessage::info(msg)) -} - -pub(crate) fn send_error_info>(msg: S) -> ELSResult<()> { - send(&ShowMessage::error(msg)) -} - -pub(crate) fn send_error>(id: Option, code: i64, msg: S) -> ELSResult<()> { - send(&ErrorMessage::new( - id, - json!({ "code": code, "message": msg.into() }), - )) -} - -pub(crate) fn send_invalid_req_error() -> ELSResult<()> { - send_error(None, -32601, "received an invalid request") -} - #[derive(Debug)] pub struct AnalysisResult { pub ast: Module, @@ -328,10 +279,31 @@ pub struct Server, + pub(crate) stdout_redirect: Option>, pub(crate) _parser: std::marker::PhantomData Parser>, pub(crate) _checker: std::marker::PhantomData Checker>, } +impl RedirectableStdout for Server { + fn sender(&self) -> Option<&mpsc::Sender> { + self.stdout_redirect.as_ref() + } +} + +impl LangServer for Server { + fn dispatch(&mut self, msg: impl Into) -> Result<(), Box> { + self.dispatch(msg.into()) + } +} + +impl Server { + #[allow(unused)] + pub fn bind_fake_client() -> FakeClient { + let (sender, receiver) = std::sync::mpsc::channel(); + FakeClient::new(Server::new(ErgConfig::default(), Some(sender)), receiver) + } +} + impl Clone for Server { fn clone(&self) -> Self { Self { @@ -347,6 +319,7 @@ impl Clone for Server { modules: self.modules.clone(), analysis_result: self.analysis_result.clone(), channels: self.channels.clone(), + stdout_redirect: self.stdout_redirect.clone(), _parser: std::marker::PhantomData, _checker: std::marker::PhantomData, } @@ -354,7 +327,7 @@ impl Clone for Server { } impl Server { - pub fn new(cfg: ErgConfig) -> Self { + pub fn new(cfg: ErgConfig, stdout_redirect: Option>) -> Self { Self { comp_cache: CompletionCache::new(cfg.copy()), cfg, @@ -364,10 +337,11 @@ impl Server { client_answers: Shared::new(Dict::new()), disabled_features: vec![], opt_features: vec![], - file_cache: FileCache::new(), + file_cache: FileCache::new(stdout_redirect.clone()), modules: ModuleCache::new(), analysis_result: AnalysisResultCache::new(), channels: None, + stdout_redirect, _parser: std::marker::PhantomData, _checker: std::marker::PhantomData, } @@ -377,7 +351,7 @@ impl Server { loop { let msg = self.read_message()?; if let Err(err) = self.dispatch(msg) { - send_error_info(format!("err: {err:?}"))?; + self.send_error_info(format!("err: {err:?}"))?; } } // Ok(()) @@ -391,12 +365,24 @@ impl Server { } } + fn read_line(&self) -> io::Result { + let mut line = String::new(); + stdin().lock().read_line(&mut line)?; + Ok(line) + } + + fn read_exact(&self, len: usize) -> io::Result> { + let mut buf = vec![0; len]; + stdin().lock().read_exact(&mut buf)?; + Ok(buf) + } + #[allow(clippy::field_reassign_with_default)] fn init(&mut self, msg: &Value, id: i64) -> ELSResult<()> { - send_log("initializing ELS")?; + self.send_log("initializing ELS")?; if msg.get("params").is_some() && msg["params"].get("capabilities").is_some() { self.init_params = InitializeParams::deserialize(&msg["params"])?; - // send_log(format!("set client capabilities: {:?}", self.client_capas))?; + // self.send_log(format!("set client capabilities: {:?}", self.client_capas))?; } let mut args = self.cfg.runtime_args.iter(); while let Some(&arg) = args.next() { @@ -413,7 +399,7 @@ impl Server { let mut result = InitializeResult::default(); result.capabilities = self.init_capabilities(); self.init_services(); - send(&json!({ + self.send_stdout(&json!({ "jsonrpc": "2.0", "id": id, "result": result, @@ -520,7 +506,7 @@ impl Server { section: Some("files.autoSave".to_string()), }], }; - send(&json!({ + self.send_stdout(&json!({ "jsonrpc": "2.0", "id": ASK_AUTO_SAVE_ID, "method": "workspace/configuration", @@ -606,13 +592,13 @@ impl Server { } fn exit(&self) -> ELSResult<()> { - send_log("exiting ELS")?; + self.send_log("exiting ELS")?; std::process::exit(0); } fn shutdown(&self, id: i64) -> ELSResult<()> { - send_log("shutting down ELS")?; - send(&json!({ + self.send_log("shutting down ELS")?; + self.send_stdout(&json!({ "jsonrpc": "2.0", "id": id, "result": json!(null), @@ -634,7 +620,7 @@ impl Server { // Read in the "Content-Length: xx" part. let mut size: Option = None; loop { - let buffer = read_line()?; + let buffer = self.read_line()?; // End of input. if buffer.is_empty() { @@ -689,7 +675,7 @@ impl Server { } }; - let content = read_exact(size)?; + let content = self.read_exact(size)?; let s = String::from_utf8(content) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; @@ -704,7 +690,7 @@ impl Server { (Some(id), Some(method)) => self.handle_request(&msg, id, method), (Some(id), None) => self.handle_response(id, &msg), (None, Some(notification)) => self.handle_notification(&msg, notification), - _ => send_invalid_req_error(), + _ => self.send_invalid_req_error(), } } @@ -735,10 +721,10 @@ impl Server { match msg { WorkerMessage::Request(id, params) => match handler(&mut _self, params) { Ok(result) => { - let _ = send(&LSPResult::new(id, result)); + let _ = _self.send_stdout(&LSPResult::new(id, result)); } Err(err) => { - let _ = send(&ErrorMessage::new( + let _ = _self.send_stdout(&ErrorMessage::new( Some(id), format!("err from {}: {err}", type_name::()).into(), )); @@ -787,7 +773,7 @@ impl Server { } CallHierarchyPrepare::METHOD => self.parse_send::(id, msg), FoldingRangeRequest::METHOD => self.parse_send::(id, msg), - other => send_error(Some(id), -32600, format!("{other} is not supported")), + other => self.send_error(Some(id), -32600, format!("{other} is not supported")), } } @@ -795,13 +781,13 @@ impl Server { match method { "initialized" => { self.ask_auto_save()?; - send_log("successfully bound") + self.send_log("successfully bound") } "exit" => self.exit(), "textDocument/didOpen" => { let params = DidOpenTextDocumentParams::deserialize(msg["params"].clone())?; let uri = NormalizedUrl::new(params.text_document.uri); - send_log(format!("{method}: {uri}"))?; + self.send_log(format!("{method}: {uri}"))?; let code = params.text_document.text; let ver = params.text_document.version; self.file_cache.update(&uri, code.clone(), Some(ver)); @@ -810,7 +796,7 @@ impl Server { "textDocument/didSave" => { let uri = NormalizedUrl::parse(msg["params"]["textDocument"]["uri"].as_str().unwrap())?; - send_log(format!("{method}: {uri}"))?; + self.send_log(format!("{method}: {uri}"))?; let code = self.file_cache.get_entire_code(&uri)?; self.clear_cache(&uri); self.check_file(uri, code) @@ -831,7 +817,7 @@ impl Server { self.file_cache.incremental_update(params); Ok(()) } - _ => send_log(format!("received notification: {method}")), + _ => self.send_log(format!("received notification: {method}")), } } @@ -845,7 +831,7 @@ impl Server { .send(WorkerMessage::Request(0, ()))?; } _ => { - _log!("msg: {msg}"); + _log!(self, "msg: {msg}"); if msg.get("error").is_none() { self.client_answers.borrow_mut().insert(id, msg.clone()); } @@ -947,7 +933,7 @@ impl Server { .file_cache .get_token_relatively(uri, attr_marker_pos, -2); if let Some(token) = maybe_token { - // send_log(format!("token: {token}"))?; + // self.send_log(format!("token: {token}"))?; let mut ctxs = vec![]; if let Some(visitor) = self.get_visitor(uri) { if let Some(expr) = @@ -965,12 +951,12 @@ impl Server { ctxs.extend(singular_ctxs); } } else { - _log!("expr not found: {token}"); + _log!(self, "expr not found: {token}"); } } Ok(ctxs) } else { - send_log("token not found")?; + self.send_log("token not found")?; Ok(vec![]) } } diff --git a/crates/els/sig_help.rs b/crates/els/sig_help.rs index 720b0b8f1..4c08ef676 100644 --- a/crates/els/sig_help.rs +++ b/crates/els/sig_help.rs @@ -11,7 +11,7 @@ use lsp_types::{ }; use crate::hir_visitor::GetExprKind; -use crate::server::{send_log, ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::{loc_to_pos, pos_to_loc, NormalizedUrl}; #[derive(Debug, Clone, Copy, PartialEq, Eq)] @@ -52,7 +52,7 @@ impl Server { &mut self, params: SignatureHelpParams, ) -> ELSResult> { - send_log(format!("signature help requested: {params:?}"))?; + self.send_log(format!("signature help requested: {params:?}"))?; let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri); let pos = params.text_document_position_params.position; if params.context.as_ref().map(|ctx| &ctx.trigger_kind) @@ -83,7 +83,7 @@ impl Server { offset: isize, ) -> Option<(Token, Expr)> { let token = self.file_cache.get_token_relatively(uri, pos, offset)?; - crate::_log!("token: {token}"); + crate::_log!(self, "token: {token}"); if let Some(visitor) = self.get_visitor(uri) { if let Some(expr) = visitor.get_min_expr(loc_to_pos(token.loc())?) { return Some((token, expr.clone())); @@ -140,7 +140,7 @@ impl Server { ctx: &SignatureHelpContext, ) -> Option { if let Some(token) = self.file_cache.get_token(uri, pos) { - crate::_log!("token: {token}"); + crate::_log!(self, "token: {token}"); if let Some(call) = self.get_min::(uri, pos) { if call.ln_begin() > token.ln_begin() || call.ln_end() < token.ln_end() { return None; @@ -148,10 +148,10 @@ impl Server { let nth = self.nth(uri, &call, pos) as u32; return self.make_sig_help(call.obj.as_ref(), nth); } else { - crate::_log!("failed to get the call"); + crate::_log!(self, "failed to get the call"); } } else { - crate::_log!("failed to get the token"); + crate::_log!(self, "failed to get the token"); } ctx.active_signature_help.clone() } @@ -160,7 +160,7 @@ impl Server { if let Some((_token, Expr::Accessor(acc))) = self.get_min_expr(uri, pos, -2) { return self.make_sig_help(&acc, 0); } else { - crate::_log!("lex error occurred"); + crate::_log!(self, "lex error occurred"); } None } @@ -171,7 +171,7 @@ impl Server { let help = self.make_sig_help(call.obj.as_ref(), nth); return help; } else { - crate::_log!("failed to get continuous help"); + crate::_log!(self, "failed to get continuous help"); } None } diff --git a/crates/els/symbol.rs b/crates/els/symbol.rs index 3375a7a2b..dba784a6f 100644 --- a/crates/els/symbol.rs +++ b/crates/els/symbol.rs @@ -12,7 +12,7 @@ use lsp_types::{ }; use crate::_log; -use crate::server::{ELSResult, Server}; +use crate::server::{ELSResult, RedirectableStdout, Server}; use crate::util::{abs_loc_to_lsp_loc, loc_to_range, NormalizedUrl}; pub(crate) fn symbol_kind(vi: &VarInfo) -> SymbolKind { @@ -35,7 +35,7 @@ impl Server { &mut self, params: WorkspaceSymbolParams, ) -> ELSResult>> { - _log!("workspace symbol requested: {params:?}"); + _log!(self, "workspace symbol requested: {params:?}"); let mut res = vec![]; for module in self.modules.values() { for (name, vi) in module.context.local_dir() { @@ -74,7 +74,7 @@ impl Server { &mut self, params: DocumentSymbolParams, ) -> ELSResult> { - _log!("document symbol requested: {params:?}"); + _log!(self, "document symbol requested: {params:?}"); let uri = NormalizedUrl::new(params.text_document.uri); if let Some(result) = self.analysis_result.get(&uri) { if let Some(hir) = &result.artifact.object { diff --git a/crates/els/tests/test.rs b/crates/els/tests/test.rs index 41f1e9411..6d39e657b 100644 --- a/crates/els/tests/test.rs +++ b/crates/els/tests/test.rs @@ -1,428 +1,38 @@ -use std::fs::File; -use std::io::Read; use std::path::Path; use lsp_types::{ - CompletionContext, CompletionParams, CompletionResponse, CompletionTriggerKind, - ConfigurationParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams, - DocumentSymbolParams, DocumentSymbolResponse, FoldingRange, FoldingRangeKind, - FoldingRangeParams, GotoDefinitionParams, Hover, HoverContents, HoverParams, Location, - MarkedString, Position, Range, ReferenceContext, ReferenceParams, RenameParams, SignatureHelp, - SignatureHelpContext, SignatureHelpParams, SignatureHelpTriggerKind, - TextDocumentContentChangeEvent, TextDocumentIdentifier, TextDocumentItem, - TextDocumentPositionParams, Url, VersionedTextDocumentIdentifier, WorkspaceEdit, + CompletionResponse, DocumentSymbolResponse, FoldingRange, FoldingRangeKind, + GotoDefinitionResponse, HoverContents, MarkedString, }; -use serde::de::Deserialize; -use serde_json::{json, Value}; - -use els::{NormalizedUrl, Server, TRIGGER_CHARS}; -use erg_common::config::ErgConfig; -use erg_common::spawn::safe_yield; - const FILE_A: &str = "tests/a.er"; const FILE_B: &str = "tests/b.er"; const FILE_IMPORTS: &str = "tests/imports.er"; -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(), - } -} - -fn abs_pos(uri: Url, line: u32, col: u32) -> TextDocumentPositionParams { - TextDocumentPositionParams { - text_document: TextDocumentIdentifier::new(uri), - position: Position { - line, - character: col, - }, - } -} - -fn single_range(line: u32, from: u32, to: u32) -> Range { - Range { - start: Position { - line, - character: from, - }, - end: Position { - line, - character: to, - }, - } -} - -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 struct DummyClient { - stdout_buffer: gag::BufferRedirect, - ver: i32, - server: Server, -} - -impl Default for DummyClient { - fn default() -> Self { - Self::new() - } -} - -impl DummyClient { - pub fn new() -> Self { - let stdout_buffer = loop { - // wait until the other thread is finished - match gag::BufferRedirect::stdout() { - Ok(stdout_buffer) => break stdout_buffer, - Err(_) => safe_yield(), - } - }; - DummyClient { - stdout_buffer, - ver: 0, - server: Server::new(ErgConfig::default()), - } - } - - /// the server periodically outputs health check messages - fn wait_outputs(&mut self, mut size: usize) -> Result> { - let mut buf = String::new(); - loop { - self.stdout_buffer.read_to_string(&mut buf)?; - if buf.is_empty() { - safe_yield(); - } else { - size -= 1; - if size == 0 { - break; - } - } - } - Ok(buf) - } - - fn wait_for(&mut self) -> Result> - where - R: Deserialize<'static>, - { - loop { - let mut buf = String::new(); - self.stdout_buffer.read_to_string(&mut buf)?; - for msg in parse_msgs(&buf) { - if msg.get("method").is_some_and(|_| msg.get("id").is_some()) { - self.handle_server_request(&msg); - } - if let Some(result) = msg - .get("result") - .cloned() - .and_then(|res| R::deserialize(res).ok()) - { - return Ok(result); - } - } - safe_yield(); - } - } - - fn handle_server_request(&mut self, msg: &Value) { - if let Ok(_params) = ConfigurationParams::deserialize(&msg["params"]) { - let msg = json!({ - "jsonrpc": "2.0", - "id": msg["id"].as_i64().unwrap(), - "result": null, - }); - self.server.dispatch(msg).unwrap(); - } - } - - fn request_initialize(&mut self) -> Result> { - let msg = json!({ - "jsonrpc": "2.0", - "id": 0, - "method": "initialize", - }); - self.server.dispatch(msg)?; - let buf = self.wait_outputs(2)?; - // eprintln!("`{}`", buf); - Ok(buf) - } - - 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)?; - let buf = self.wait_outputs(1)?; - // eprintln!("open: `{}`", buf); - Ok(buf) - } - - 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)?; - let buf = self.wait_outputs(1)?; - // eprintln!("{}: `{}`", line!(), buf); - Ok(buf) - } - - 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 TRIGGER_CHARS.contains(&character) { - CompletionTriggerKind::TRIGGER_CHARACTER - } else { - CompletionTriggerKind::INVOKED - }; - let trigger_character = TRIGGER_CHARS - .contains(&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": 1, - "method": "textDocument/completion", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::() - } - - 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": 1, - "method": "textDocument/rename", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::() - } - - 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": 1, - "method": "textDocument/signatureHelp", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::() - } - - 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": 1, - "method": "textDocument/hover", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::() - } - - fn request_references( - &mut self, - uri: Url, - line: u32, - col: u32, - ) -> Result, Box> { - 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": 1, - "method": "textDocument/references", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::>() - } - - 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": 1, - "method": "textDocument/definition", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::() - } - - fn request_folding_range( - &mut self, - uri: Url, - ) -> Result>, Box> { - 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": 1, - "method": "textDocument/foldingRange", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::>>() - } - - fn request_document_symbols( - &mut self, - uri: Url, - ) -> Result, Box> { - 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": 1, - "method": "textDocument/documentSymbol", - "params": params, - }); - self.server.dispatch(msg)?; - self.wait_for::>() - } -} +use els::{NormalizedUrl, Server}; +use molc::{add_char, oneline_range}; #[test] fn test_open() -> Result<(), Box> { - let mut client = DummyClient::new(); + let mut client = Server::bind_fake_client(); client.request_initialize()?; - let result = client.notify_open(FILE_A)?; - assert!(result.contains("tests/a.er passed, found warns: 0")); + client.notify_open(FILE_A)?; + client.wait_messages(3)?; + assert!(client.responses.iter().any(|val| val + .to_string() + .contains("tests/a.er passed, found warns: 0"))); Ok(()) } #[test] fn test_completion() -> Result<(), Box> { - let mut client = DummyClient::new(); + let mut client = Server::bind_fake_client(); client.request_initialize()?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; client.notify_open(FILE_A)?; client.notify_change(uri.clone().raw(), add_char(2, 0, "x"))?; client.notify_change(uri.clone().raw(), add_char(2, 1, "."))?; let resp = client.request_completion(uri.raw(), 2, 2, ".")?; - if let CompletionResponse::Array(items) = resp { + if let Some(CompletionResponse::Array(items)) = resp { assert!(items.len() >= 40); assert!(items.iter().any(|item| item.label == "abs")); Ok(()) @@ -433,13 +43,13 @@ fn test_completion() -> Result<(), Box> { #[test] fn test_neighbor_completion() -> Result<(), Box> { - let mut client = DummyClient::new(); + let mut client = Server::bind_fake_client(); client.request_initialize()?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; client.notify_open(FILE_A)?; client.notify_open(FILE_B)?; let resp = client.request_completion(uri.raw(), 2, 0, "n")?; - if let CompletionResponse::Array(items) = resp { + if let Some(CompletionResponse::Array(items)) = resp { assert!(items.len() >= 40); assert!(items .iter() @@ -452,11 +62,13 @@ fn test_neighbor_completion() -> Result<(), Box> { #[test] fn test_rename() -> Result<(), Box> { - let mut client = DummyClient::new(); + let mut client = Server::bind_fake_client(); client.request_initialize()?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; client.notify_open(FILE_A)?; - let edit = client.request_rename(uri.clone().raw(), 1, 5, "y")?; + let edit = client + .request_rename(uri.clone().raw(), 1, 5, "y")? + .unwrap(); assert!(edit .changes .is_some_and(|changes| changes.values().next().unwrap().len() == 2)); @@ -465,13 +77,15 @@ fn test_rename() -> Result<(), Box> { #[test] fn test_signature_help() -> Result<(), Box> { - let mut client = DummyClient::new(); + let mut client = Server::bind_fake_client(); client.request_initialize()?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; client.notify_open(FILE_A)?; client.notify_change(uri.clone().raw(), add_char(2, 0, "assert"))?; client.notify_change(uri.clone().raw(), add_char(2, 6, "("))?; - let help = client.request_signature_help(uri.raw(), 2, 7, "(")?; + let help = client + .request_signature_help(uri.raw(), 2, 7, "(")? + .unwrap(); assert_eq!(help.signatures.len(), 1); let sig = &help.signatures[0]; assert_eq!(sig.label, "::assert: (test: Bool, msg := Str) -> NoneType"); @@ -481,11 +95,11 @@ fn test_signature_help() -> Result<(), Box> { #[test] fn test_hover() -> Result<(), Box> { - let mut client = DummyClient::new(); + let mut client = Server::bind_fake_client(); client.request_initialize()?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; client.notify_open(FILE_A)?; - let hover = client.request_hover(uri.raw(), 1, 4)?; + let hover = client.request_hover(uri.raw(), 1, 4)?.unwrap(); let HoverContents::Array(contents) = hover.contents else { todo!() }; @@ -506,30 +120,34 @@ fn test_hover() -> Result<(), Box> { #[test] fn test_references() -> Result<(), Box> { - let mut client = DummyClient::new(); + let mut client = Server::bind_fake_client(); client.request_initialize()?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; client.notify_open(FILE_A)?; - let locations = client.request_references(uri.raw(), 1, 4)?; + let locations = client.request_references(uri.raw(), 1, 4)?.unwrap(); assert_eq!(locations.len(), 1); - assert_eq!(&locations[0].range, &single_range(1, 4, 5)); + assert_eq!(&locations[0].range, &oneline_range(1, 4, 5)); Ok(()) } #[test] fn test_goto_definition() -> Result<(), Box> { - let mut client = DummyClient::new(); + let mut client = Server::bind_fake_client(); client.request_initialize()?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; client.notify_open(FILE_A)?; - let location = client.request_goto_definition(uri.raw(), 1, 4)?; - assert_eq!(&location.range, &single_range(0, 0, 1)); + let Some(GotoDefinitionResponse::Scalar(location)) = + client.request_goto_definition(uri.raw(), 1, 4)? + else { + todo!() + }; + assert_eq!(&location.range, &oneline_range(0, 0, 1)); Ok(()) } #[test] fn test_folding_range() -> Result<(), Box> { - let mut client = DummyClient::new(); + let mut client = Server::bind_fake_client(); client.request_initialize()?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_IMPORTS).canonicalize()?)?; client.notify_open(FILE_IMPORTS)?; @@ -550,7 +168,7 @@ fn test_folding_range() -> Result<(), Box> { #[test] fn test_document_symbol() -> Result<(), Box> { - let mut client = DummyClient::new(); + let mut client = Server::bind_fake_client(); client.request_initialize()?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; client.notify_open(FILE_A)?; diff --git a/src/main.rs b/src/main.rs index cd18fbe70..9686b3bd7 100644 --- a/src/main.rs +++ b/src/main.rs @@ -34,7 +34,7 @@ fn run() { #[cfg(feature = "els")] { use els::ErgLanguageServer; - let mut server = ErgLanguageServer::new(cfg); + let mut server = ErgLanguageServer::new(cfg, None); server.run().unwrap(); ExitStatus::OK }