feat: add molc

* use molc for ELS tests
This commit is contained in:
Shunsuke Shibayama 2023-09-08 02:13:13 +09:00
parent 6ca5e07191
commit dcb42f68b9
30 changed files with 884 additions and 797 deletions

125
Cargo.lock generated
View file

@ -55,12 +55,6 @@ version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bitflags"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4682ae6287fcf752ecaabbfcc7b6f9b72aa33933dc23a554d853aea8eea8635"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.0.83"
@ -82,7 +76,7 @@ version = "0.25.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67" checksum = "e64e6c0fbe2c17357405f7c758c1ef960fce08bdfb2c03d88d2a18d7e09c4b67"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags",
"crossterm_winapi", "crossterm_winapi",
"libc", "libc",
"mio", "mio",
@ -107,8 +101,8 @@ version = "0.1.32-nightly.2"
dependencies = [ dependencies = [
"erg_common", "erg_common",
"erg_compiler", "erg_compiler",
"gag",
"lsp-types", "lsp-types",
"molc",
"serde", "serde",
"serde_json", "serde_json",
] ]
@ -149,44 +143,6 @@ dependencies = [
"unicode-xid", "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]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.0" version = "1.2.0"
@ -196,16 +152,6 @@ dependencies = [
"percent-encoding", "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]] [[package]]
name = "gimli" name = "gimli"
version = "0.28.0" version = "0.28.0"
@ -234,12 +180,6 @@ version = "0.2.147"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3"
[[package]]
name = "linux-raw-sys"
version = "0.4.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "57bcfdad1b858c2db7c38303a6d2ad4dfaf5eb53dfeb0910128b2c26d6158503"
[[package]] [[package]]
name = "lock_api" name = "lock_api"
version = "0.4.10" version = "0.4.10"
@ -262,7 +202,7 @@ version = "0.93.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9be6e9c7e2d18f651974370d7aff703f9513e0df6e464fd795660edc77e6ca51" checksum = "9be6e9c7e2d18f651974370d7aff703f9513e0df6e464fd795660edc77e6ca51"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags",
"serde", "serde",
"serde_json", "serde_json",
"serde_repr", "serde_repr",
@ -305,13 +245,22 @@ dependencies = [
"windows-sys", "windows-sys",
] ]
[[package]]
name = "molc"
version = "0.1.0"
dependencies = [
"lsp-types",
"serde",
"serde_json",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.23.2" version = "0.23.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c" checksum = "8f3790c00a0150112de0f4cd161e3d7fc4b2d8a5542ffc35f099a2562aecb35c"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags",
"cc", "cc",
"cfg-if", "cfg-if",
"libc", "libc",
@ -386,7 +335,7 @@ version = "0.3.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29" checksum = "567664f262709473930a4bf9e51bf2ebf3348f2e748ccc50dea20646858f8f29"
dependencies = [ dependencies = [
"bitflags 1.3.2", "bitflags",
] ]
[[package]] [[package]]
@ -395,19 +344,6 @@ version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" 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]] [[package]]
name = "ryu" name = "ryu"
version = "1.0.15" version = "1.0.15"
@ -509,39 +445,6 @@ dependencies = [
"unicode-ident", "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]] [[package]]
name = "thread_local" name = "thread_local"
version = "1.1.7" version = "1.1.7"

View file

@ -17,6 +17,7 @@ members = [
"crates/erg_compiler", "crates/erg_compiler",
"crates/erg_parser", "crates/erg_parser",
"crates/els", "crates/els",
"crates/molc"
] ]
[workspace.package] [workspace.package]
@ -68,6 +69,7 @@ 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_parser = { version = "0.6.20-nightly.2", path = "./crates/erg_parser" }
erg_compiler = { version = "0.6.20-nightly.2", path = "./crates/erg_compiler" } erg_compiler = { version = "0.6.20-nightly.2", path = "./crates/erg_compiler" }
els = { version = "0.1.32-nightly.2", path = "./crates/els" } els = { version = "0.1.32-nightly.2", path = "./crates/els" }
molc = { version = "0.1.0", path = "./crates/molc" }
[dependencies] [dependencies]
erg_common = { workspace = true } erg_common = { workspace = true }

View file

@ -23,13 +23,11 @@ experimental = ["erg_common/experimental", "erg_compiler/experimental"]
[dependencies] [dependencies]
erg_common = { workspace = true, features = ["els"] } erg_common = { workspace = true, features = ["els"] }
erg_compiler = { workspace = true, features = ["els"] } erg_compiler = { workspace = true, features = ["els"] }
molc = { workspace = true }
serde = { version = "1.0", features = ["derive"] } serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.85" serde_json = "1.0.85"
lsp-types = { version = "0.93.2", features = ["proposed"] } lsp-types = { version = "0.93.2", features = ["proposed"] }
[dev-dependencies]
gag = "1"
[lib] [lib]
path = "lib.rs" path = "lib.rs"

View file

@ -11,7 +11,7 @@ use lsp_types::{
}; };
use crate::_log; use crate::_log;
use crate::server::{ELSResult, Server}; use crate::server::{ELSResult, RedirectableStdout, Server};
use crate::symbol::symbol_kind; use crate::symbol::symbol_kind;
use crate::util::{abs_loc_to_lsp_loc, loc_to_pos, NormalizedUrl}; use crate::util::{abs_loc_to_lsp_loc, loc_to_pos, NormalizedUrl};
@ -35,7 +35,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
params: CallHierarchyIncomingCallsParams, params: CallHierarchyIncomingCallsParams,
) -> ELSResult<Option<Vec<CallHierarchyIncomingCall>>> { ) -> ELSResult<Option<Vec<CallHierarchyIncomingCall>>> {
let mut res = vec![]; 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 { let Some(data) = params.item.data.as_ref().and_then(|d| d.as_str()) else {
return Ok(None); return Ok(None);
}; };
@ -80,7 +80,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: CallHierarchyOutgoingCallsParams, params: CallHierarchyOutgoingCallsParams,
) -> ELSResult<Option<Vec<CallHierarchyOutgoingCall>>> { ) -> ELSResult<Option<Vec<CallHierarchyOutgoingCall>>> {
_log!("call hierarchy outgoing calls requested: {params:?}"); _log!(self, "call hierarchy outgoing calls requested: {params:?}");
Ok(None) Ok(None)
} }
@ -88,7 +88,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: CallHierarchyPrepareParams, params: CallHierarchyPrepareParams,
) -> ELSResult<Option<Vec<CallHierarchyItem>>> { ) -> ELSResult<Option<Vec<CallHierarchyItem>>> {
_log!("call hierarchy prepare requested: {params:?}"); _log!(self, "call hierarchy prepare requested: {params:?}");
let mut res = vec![]; let mut res = vec![];
let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri); let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri);
let pos = params.text_document_position_params.position; let pos = params.text_document_position_params.position;

View file

@ -14,7 +14,7 @@ use lsp_types::{
Position, Range, TextEdit, Url, WorkspaceEdit, Position, Range, TextEdit, Url, WorkspaceEdit,
}; };
use crate::server::{send_log, ELSResult, Server}; use crate::server::{ELSResult, RedirectableStdout, Server};
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> { impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
@ -29,11 +29,11 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
}; };
let mut map = HashMap::new(); let mut map = HashMap::new();
let Some(visitor) = self.get_visitor(&uri) else { let Some(visitor) = self.get_visitor(&uri) else {
send_log("visitor not found")?; self.send_log("visitor not found")?;
return Ok(None); return Ok(None);
}; };
let Some(result) = self.analysis_result.get(&uri) else { let Some(result) = self.analysis_result.get(&uri) else {
send_log("artifact not found")?; self.send_log("artifact not found")?;
return Ok(None); return Ok(None);
}; };
let warns = result let warns = result
@ -50,7 +50,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
match visitor.get_min_expr(pos) { match visitor.get_min_expr(pos) {
Some(Expr::Def(def)) => { Some(Expr::Def(def)) => {
let Some(mut range) = util::loc_to_range(def.loc()) else { let Some(mut range) = util::loc_to_range(def.loc()) else {
send_log("range not found")?; self.send_log("range not found")?;
continue; continue;
}; };
let next = lsp_types::Range { let next = lsp_types::Range {
@ -72,7 +72,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} }
Some(";") => range.end.character += 1, Some(";") => range.end.character += 1,
Some(other) => { Some(other) => {
send_log(format!("? {other}"))?; self.send_log(format!("? {other}"))?;
} }
} }
let edit = TextEdit::new(range, "".to_string()); let edit = TextEdit::new(range, "".to_string());
@ -228,7 +228,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: CodeActionParams, params: CodeActionParams,
) -> ELSResult<Option<CodeActionResponse>> { ) -> ELSResult<Option<CodeActionResponse>> {
send_log(format!("code action requested: {params:?}"))?; self.send_log(format!("code action requested: {params:?}"))?;
let result = match params let result = match params
.context .context
.only .only
@ -238,7 +238,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
Some("quickfix") => self.send_quick_fix(&params)?, Some("quickfix") => self.send_quick_fix(&params)?,
None => self.send_normal_action(&params)?, None => self.send_normal_action(&params)?,
Some(other) => { Some(other) => {
send_log(&format!("Unknown code action requested: {other}"))?; self.send_log(&format!("Unknown code action requested: {other}"))?;
vec![] vec![]
} }
}; };
@ -254,7 +254,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
action: CodeAction, action: CodeAction,
) -> ELSResult<CodeAction> { ) -> ELSResult<CodeAction> {
send_log(format!("code action resolve requested: {action:?}"))?; self.send_log(format!("code action resolve requested: {action:?}"))?;
match &action.title[..] { match &action.title[..] {
"Extract into function" | "Extract into variable" => { "Extract into function" | "Extract into variable" => {
self.resolve_extract_action(action) self.resolve_extract_action(action)

View file

@ -4,7 +4,7 @@ use erg_compiler::hir::Expr;
use lsp_types::{CodeLens, CodeLensParams}; use lsp_types::{CodeLens, CodeLensParams};
use crate::server::{send_log, ELSResult, Server}; use crate::server::{ELSResult, RedirectableStdout, Server};
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> { impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
@ -12,7 +12,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: CodeLensParams, params: CodeLensParams,
) -> ELSResult<Option<Vec<CodeLens>>> { ) -> ELSResult<Option<Vec<CodeLens>>> {
send_log("code lens requested")?; self.send_log("code lens requested")?;
let uri = NormalizedUrl::new(params.text_document.uri); let uri = NormalizedUrl::new(params.text_document.uri);
// TODO: parallelize // TODO: parallelize
let result = [ let result = [

View file

@ -8,7 +8,7 @@ use erg_compiler::hir::Expr;
use lsp_types::{Command, ExecuteCommandParams, Location, Url}; use lsp_types::{Command, ExecuteCommandParams, Location, Url};
use crate::_log; use crate::_log;
use crate::server::{ELSResult, Server}; use crate::server::{ELSResult, RedirectableStdout, Server};
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> { impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
@ -16,11 +16,11 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: ExecuteCommandParams, params: ExecuteCommandParams,
) -> ELSResult<Option<Value>> { ) -> ELSResult<Option<Value>> {
_log!("command requested: {}", params.command); _log!(self, "command requested: {}", params.command);
#[allow(clippy::match_single_binding)] #[allow(clippy::match_single_binding)]
match &params.command[..] { match &params.command[..] {
other => { other => {
_log!("unknown command {other}: {params:?}"); _log!(self, "unknown command {other}: {params:?}");
Ok(None) Ok(None)
} }
} }

View file

@ -31,7 +31,7 @@ use lsp_types::{
MarkupContent, MarkupKind, Position, Range, TextEdit, MarkupContent, MarkupKind, Position, Range, TextEdit,
}; };
use crate::server::{send_log, ELSResult, Server}; use crate::server::{ELSResult, RedirectableStdout, Server};
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
fn comp_item_kind(vi: &VarInfo) -> CompletionItemKind { fn comp_item_kind(vi: &VarInfo) -> CompletionItemKind {
@ -355,7 +355,7 @@ impl CompletionCache {
let clone = cache.clone(); let clone = cache.clone();
spawn_new_thread( spawn_new_thread(
move || { move || {
crate::_log!("load_modules"); // crate::_log!("load_modules");
let major_mods = [ let major_mods = [
"argparse", "argparse",
"array", "array",
@ -490,7 +490,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: CompletionParams, params: CompletionParams,
) -> ELSResult<Option<CompletionResponse>> { ) -> ELSResult<Option<CompletionResponse>> {
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 uri = NormalizedUrl::new(params.text_document_position.text_document.uri);
let path = util::uri_to_path(&uri); let path = util::uri_to_path(&uri);
let pos = params.text_document_position.position; let pos = params.text_document_position.position;
@ -514,7 +514,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
Some("(") => CompletionKind::LParen, Some("(") => CompletionKind::LParen,
_ => CompletionKind::Local, _ => CompletionKind::Local,
}; };
send_log(format!("CompletionKind: {comp_kind:?}"))?; self.send_log(format!("CompletionKind: {comp_kind:?}"))?;
let mut result: Vec<CompletionItem> = vec![]; let mut result: Vec<CompletionItem> = vec![];
let mut already_appeared = Set::new(); let mut already_appeared = Set::new();
let contexts = if comp_kind.should_be_local() { let contexts = if comp_kind.should_be_local() {
@ -629,7 +629,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} }
result.extend(self.neighbor_completion(&uri, arg_pt, &mut already_appeared)); 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))) Ok(Some(CompletionResponse::Array(result)))
} }
@ -637,7 +637,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
mut item: CompletionItem, mut item: CompletionItem,
) -> ELSResult<CompletionItem> { ) -> ELSResult<CompletionItem> {
send_log(format!("completion resolve requested: {item:?}"))?; self.send_log(format!("completion resolve requested: {item:?}"))?;
if let Some(data) = &item.data { if let Some(data) = &item.data {
let mut contents = vec![]; let mut contents = vec![];
let Ok(def_loc) = data.as_str().unwrap_or_default().parse::<AbsLocation>() else { let Ok(def_loc) = data.as_str().unwrap_or_default().parse::<AbsLocation>() else {

View file

@ -10,7 +10,7 @@ use erg_compiler::varinfo::VarInfo;
use lsp_types::{GotoDefinitionParams, GotoDefinitionResponse, Location, Position, Url}; 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}; use crate::util::{self, NormalizedUrl};
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> { impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
@ -20,12 +20,12 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
token: &Token, token: &Token,
) -> ELSResult<Option<VarInfo>> { ) -> ELSResult<Option<VarInfo>> {
if !token.category_is(TokenCategory::Symbol) { if !token.category_is(TokenCategory::Symbol) {
send_log(format!("not symbol: {token}"))?; self.send_log(format!("not symbol: {token}"))?;
Ok(None) Ok(None)
} else if let Some(visitor) = self.get_visitor(uri) { } else if let Some(visitor) = self.get_visitor(uri) {
Ok(visitor.get_info(token)) Ok(visitor.get_info(token))
} else { } else {
send_log("not found")?; self.send_log("not found")?;
Ok(None) Ok(None)
} }
} }
@ -99,7 +99,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
return Ok(Some(lsp_types::Location::new(def_uri, range))); 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); return Ok(None);
} }
} }
@ -112,7 +112,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
Ok(Some(lsp_types::Location::new(def_uri, range))) Ok(Some(lsp_types::Location::new(def_uri, range)))
} }
_ => { _ => {
send_log("not found (maybe builtin)")?; self.send_log("not found (maybe builtin)")?;
Ok(None) Ok(None)
} }
} }
@ -120,7 +120,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
Ok(None) Ok(None)
} }
} else { } else {
send_log("lex error occurred")?; self.send_log("lex error occurred")?;
Ok(None) Ok(None)
} }
} }
@ -129,7 +129,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: GotoDefinitionParams, params: GotoDefinitionParams,
) -> ELSResult<Option<GotoDefinitionResponse>> { ) -> ELSResult<Option<GotoDefinitionResponse>> {
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 uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri);
let pos = params.text_document_position_params.position; let pos = params.text_document_position_params.position;
let result = self.get_definition_location(&uri, pos)?; let result = self.get_definition_location(&uri, pos)?;

View file

@ -23,7 +23,7 @@ use crate::_log;
use crate::channels::WorkerMessage; use crate::channels::WorkerMessage;
use crate::diff::{ASTDiff, HIRDiff}; use crate::diff::{ASTDiff, HIRDiff};
use crate::server::{ 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, HEALTH_CHECKER_ID,
}; };
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
@ -39,7 +39,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
uri: NormalizedUrl, uri: NormalizedUrl,
code: S, code: S,
) -> ELSResult<()> { ) -> ELSResult<()> {
send_log(format!("checking {uri}"))?; self.send_log(format!("checking {uri}"))?;
let path = util::uri_to_path(&uri); let path = util::uri_to_path(&uri);
let mode = if path.to_string_lossy().ends_with(".d.er") { let mode = if path.to_string_lossy().ends_with(".d.er") {
"declare" "declare"
@ -48,14 +48,14 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
}; };
if let Some((old, new)) = self.analysis_result.get_ast(&uri).zip(self.get_ast(&uri)) { if let Some((old, new)) = self.analysis_result.get_ast(&uri).zip(self.get_ast(&uri)) {
if ASTDiff::diff(old, &new).is_nop() { if ASTDiff::diff(old, &new).is_nop() {
crate::_log!("no changes: {uri}"); crate::_log!(self, "no changes: {uri}");
return Ok(()); return Ok(());
} }
} }
let mut checker = self.get_checker(path.clone()); let mut checker = self.get_checker(path.clone());
let artifact = match checker.build(code.into(), mode) { let artifact = match checker.build(code.into(), mode) {
Ok(artifact) => { Ok(artifact) => {
send_log(format!( self.send_log(format!(
"checking {uri} passed, found warns: {}", "checking {uri} passed, found warns: {}",
artifact.warns.len() artifact.warns.len()
))?; ))?;
@ -63,14 +63,14 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
// clear previous diagnostics // clear previous diagnostics
self.send_diagnostics(uri.clone().raw(), vec![])?; self.send_diagnostics(uri.clone().raw(), vec![])?;
for (uri, diags) in uri_and_diags.into_iter() { 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)?; self.send_diagnostics(uri, diags)?;
} }
artifact.into() artifact.into()
} }
Err(artifact) => { Err(artifact) => {
send_log(format!("found errors: {}", artifact.errors.len()))?; self.send_log(format!("found errors: {}", artifact.errors.len()))?;
send_log(format!("found warns: {}", artifact.warns.len()))?; self.send_log(format!("found warns: {}", artifact.warns.len()))?;
let diags = artifact let diags = artifact
.errors .errors
.clone() .clone()
@ -82,7 +82,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
self.send_diagnostics(uri.clone().raw(), vec![])?; self.send_diagnostics(uri.clone().raw(), vec![])?;
} }
for (uri, diags) in uri_and_diags.into_iter() { 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)?; self.send_diagnostics(uri, diags)?;
} }
artifact artifact
@ -108,12 +108,12 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
.insert(uri.clone(), AnalysisResult::new(module, artifact)); .insert(uri.clone(), AnalysisResult::new(module, artifact));
} }
if let Some(module) = checker.pop_context() { 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); self.modules.insert(uri.clone(), module);
} }
let dependents = self.dependents_of(&uri); let dependents = self.dependents_of(&uri);
for dep in dependents { for dep in dependents {
// _log!("dep: {dep}"); // _log!(self, "dep: {dep}");
let code = self.file_cache.get_entire_code(&dep)?.to_string(); let code = self.file_cache.get_entire_code(&dep)?.to_string();
self.check_file(dep, code)?; self.check_file(dep, code)?;
} }
@ -122,19 +122,19 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
pub(crate) fn quick_check_file(&mut self, uri: NormalizedUrl) -> ELSResult<()> { pub(crate) fn quick_check_file(&mut self, uri: NormalizedUrl) -> ELSResult<()> {
let Some(old) = self.analysis_result.get_ast(&uri) else { let Some(old) = self.analysis_result.get_ast(&uri) else {
crate::_log!("not found"); crate::_log!(self, "not found");
return Ok(()); return Ok(());
}; };
let Some(new) = self.get_ast(&uri) else { let Some(new) = self.get_ast(&uri) else {
crate::_log!("not found"); crate::_log!(self, "not found");
return Ok(()); return Ok(());
}; };
let ast_diff = ASTDiff::diff(old, &new); 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) { if let Some(mut lowerer) = self.steal_lowerer(&uri) {
let hir = self.analysis_result.get_mut_hir(&uri); let hir = self.analysis_result.get_mut_hir(&uri);
if let Some((hir_diff, hir)) = HIRDiff::new(ast_diff, &mut lowerer).zip(hir) { 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); hir_diff.update(hir);
} }
self.restore_lowerer(uri, lowerer); self.restore_lowerer(uri, lowerer);
@ -154,7 +154,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
.unwrap_or(err.input.path().to_path_buf()), .unwrap_or(err.input.path().to_path_buf()),
); );
let Ok(err_uri) = res_uri else { 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; continue;
}; };
let mut message = remove_style(&err.core.main_message); let mut message = remove_style(&err.core.main_message);
@ -214,13 +214,13 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
.map(|doc| doc.publish_diagnostics.is_some()) .map(|doc| doc.publish_diagnostics.is_some())
.unwrap_or(false) .unwrap_or(false)
{ {
send(&json!({ self.send_stdout(&json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"method": "textDocument/publishDiagnostics", "method": "textDocument/publishDiagnostics",
"params": params, "params": params,
}))?; }))?;
} else { } else {
send_log("the client does not support diagnostics")?; self.send_log("the client does not support diagnostics")?;
} }
Ok(()) Ok(())
} }
@ -242,7 +242,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
== Some("afterDelay") == Some("afterDelay")
}) })
{ {
_log!("Auto saving is enabled"); _log!(_self, "Auto saving is enabled");
break; break;
} }
for uri in _self.file_cache.entries() { for uri in _self.file_cache.entries() {
@ -272,14 +272,19 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
pub fn start_client_health_checker(&self, receiver: Receiver<WorkerMessage<()>>) { pub fn start_client_health_checker(&self, receiver: Receiver<WorkerMessage<()>>) {
const INTERVAL: Duration = Duration::from_secs(5); const INTERVAL: Duration = Duration::from_secs(5);
const TIMEOUT: Duration = Duration::from_secs(10); const TIMEOUT: Duration = Duration::from_secs(10);
if self.stdout_redirect.is_some() {
return;
}
let _self = self.clone();
// let mut self_ = self.clone(); // let mut self_ = self.clone();
// FIXME: close this thread when the server is restarted // FIXME: close this thread when the server is restarted
spawn_new_thread( spawn_new_thread(
move || { move || {
loop { loop {
// send_log("checking client health").unwrap(); // self.send_log("checking client health").unwrap();
let params = ConfigurationParams { items: vec![] }; let params = ConfigurationParams { items: vec![] };
send(&json!({ _self
.send_stdout(&json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"id": HEALTH_CHECKER_ID, "id": HEALTH_CHECKER_ID,
"method": "workspace/configuration", "method": "workspace/configuration",
@ -299,12 +304,12 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
break; break;
} }
Ok(_) => { Ok(_) => {
// send_log("client health check passed").unwrap(); // self.send_log("client health check passed").unwrap();
} }
Err(_) => { Err(_) => {
lsp_log!("Client health check timed out"); lsp_log!("Client health check timed out");
// lsp_log!("Restart the server"); // lsp_log!(self, "Restart the server");
// _log!("Restart the server"); // _log!(self, "Restart the server");
// send_error_info("Something went wrong, ELS has been restarted").unwrap(); // send_error_info("Something went wrong, ELS has been restarted").unwrap();
// self_.restart(); // self_.restart();
panic!("Client health check timed out"); panic!("Client health check timed out");

View file

@ -95,8 +95,8 @@ impl HIRDiff {
ASTDiff::Addition(idx, expr) => { ASTDiff::Addition(idx, expr) => {
let expr = lowerer let expr = lowerer
.lower_chunk(expr, None) .lower_chunk(expr, None)
.map_err(|err| { .map_err(|_err| {
crate::_log!("err: {err}"); // crate::_log!(self, "err: {err}");
}) })
.ok()?; .ok()?;
Some(Self::Addition(idx, expr)) Some(Self::Addition(idx, expr))
@ -112,8 +112,8 @@ impl HIRDiff {
} }
let expr = lowerer let expr = lowerer
.lower_chunk(expr, None) .lower_chunk(expr, None)
.map_err(|err| { .map_err(|_err| {
crate::_log!("err: {err}"); // crate::_log!(self, "err: {err}");
}) })
.ok()?; .ok()?;
Some(Self::Modification(idx, expr)) Some(Self::Modification(idx, expr))

View file

@ -1,5 +1,6 @@
use std::fs::File; use std::fs::File;
use std::io::Read; use std::io::Read;
use std::sync::mpsc::Sender;
use lsp_types::{ use lsp_types::{
DidChangeTextDocumentParams, FileOperationFilter, FileOperationPattern, DidChangeTextDocumentParams, FileOperationFilter, FileOperationPattern,
@ -8,6 +9,7 @@ use lsp_types::{
TextDocumentSyncKind, TextDocumentSyncOptions, Url, WorkspaceFileOperationsServerCapabilities, TextDocumentSyncKind, TextDocumentSyncOptions, Url, WorkspaceFileOperationsServerCapabilities,
WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities, WorkspaceFoldersServerCapabilities, WorkspaceServerCapabilities,
}; };
use serde_json::Value;
use erg_common::dict::Dict; use erg_common::dict::Dict;
use erg_common::shared::Shared; 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 erg_compiler::erg_parser::token::{Token, TokenCategory, TokenStream};
use crate::_log; use crate::_log;
use crate::server::ELSResult; use crate::server::{ELSResult, RedirectableStdout};
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
fn _get_code_from_uri(uri: &Url) -> ELSResult<String> { fn _get_code_from_uri(uri: &Url) -> ELSResult<String> {
@ -48,12 +50,20 @@ impl FileCacheEntry {
/// This struct can save changes in real-time & incrementally. /// This struct can save changes in real-time & incrementally.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub struct FileCache { pub struct FileCache {
stdout_redirect: Option<Sender<Value>>,
pub files: Shared<Dict<NormalizedUrl, FileCacheEntry>>, pub files: Shared<Dict<NormalizedUrl, FileCacheEntry>>,
} }
impl RedirectableStdout for FileCache {
fn sender(&self) -> Option<&Sender<Value>> {
self.stdout_redirect.as_ref()
}
}
impl FileCache { impl FileCache {
pub fn new() -> Self { pub fn new(stdout_redirect: Option<Sender<Value>>) -> Self {
Self { Self {
stdout_redirect,
files: Shared::new(Dict::new()), files: Shared::new(Dict::new()),
} }
} }
@ -230,7 +240,7 @@ impl FileCache {
let entry = ent.get(uri); let entry = ent.get(uri);
if let Some(entry) = entry { if let Some(entry) = entry {
if ver.map_or(false, |ver| ver <= entry.ver) { 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; return;
} }
} }
@ -275,7 +285,13 @@ impl FileCache {
return; return;
}; };
if entry.ver >= params.text_document.version { 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; return;
} }
let mut code = entry.code.clone(); let mut code = entry.code.clone();
@ -301,15 +317,15 @@ impl FileCache {
pub fn rename_files(&mut self, params: &RenameFilesParams) -> ELSResult<()> { pub fn rename_files(&mut self, params: &RenameFilesParams) -> ELSResult<()> {
for file in &params.files { for file in &params.files {
let Ok(old_uri) = NormalizedUrl::parse(&file.old_uri) else { 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; continue;
}; };
let Ok(new_uri) = NormalizedUrl::parse(&file.new_uri) else { 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; continue;
}; };
let Some(entry) = self.files.borrow_mut().remove(&old_uri) else { 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; continue;
}; };
self.files.borrow_mut().insert(new_uri, entry); self.files.borrow_mut().insert(new_uri, entry);

View file

@ -7,7 +7,7 @@ use erg_compiler::erg_parser::parse::Parsable;
use lsp_types::{FoldingRange, FoldingRangeKind, FoldingRangeParams}; use lsp_types::{FoldingRange, FoldingRangeKind, FoldingRangeParams};
use crate::_log; use crate::_log;
use crate::server::{ELSResult, Server}; use crate::server::{ELSResult, RedirectableStdout, Server};
use crate::util::NormalizedUrl; use crate::util::NormalizedUrl;
fn imports_range(start: &Location, end: &Location) -> Option<FoldingRange> { fn imports_range(start: &Location, end: &Location) -> Option<FoldingRange> {
@ -25,7 +25,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: FoldingRangeParams, params: FoldingRangeParams,
) -> ELSResult<Option<Vec<FoldingRange>>> { ) -> ELSResult<Option<Vec<FoldingRange>>> {
_log!("folding range requested: {params:?}"); _log!(self, "folding range requested: {params:?}");
let uri = NormalizedUrl::new(params.text_document.uri); let uri = NormalizedUrl::new(params.text_document.uri);
let mut res = vec![]; let mut res = vec![];
res.extend(self.fold_imports(&uri)); res.extend(self.fold_imports(&uri));

View file

@ -8,7 +8,7 @@ use erg_compiler::varinfo::{AbsLocation, VarInfo};
use lsp_types::{Hover, HoverContents, HoverParams, MarkedString, Url}; 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}; use crate::util::{self, NormalizedUrl};
const PROG_LANG: &str = if PYTHON_MODE { "python" } else { "erg" }; const PROG_LANG: &str = if PYTHON_MODE { "python" } else { "erg" };
@ -80,7 +80,7 @@ macro_rules! next {
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> { impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
pub(crate) fn handle_hover(&mut self, params: HoverParams) -> ELSResult<Option<Hover>> { pub(crate) fn handle_hover(&mut self, params: HoverParams) -> ELSResult<Option<Hover>> {
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 uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri);
let pos = params.text_document_position_params.position; let pos = params.text_document_position_params.position;
let mut contents = vec![]; let mut contents = vec![];
@ -176,7 +176,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} }
} }
} else { } else {
send_log("lex error")?; self.send_log("lex error")?;
} }
Ok(Some(Hover { Ok(Some(Hover {
contents: HoverContents::Array(sort_hovers(contents)), contents: HoverContents::Array(sort_hovers(contents)),

View file

@ -3,7 +3,7 @@ use erg_compiler::erg_parser::parse::Parsable;
use lsp_types::request::{GotoImplementationParams, GotoImplementationResponse}; use lsp_types::request::{GotoImplementationParams, GotoImplementationResponse};
use crate::_log; use crate::_log;
use crate::server::{ELSResult, Server}; use crate::server::{ELSResult, RedirectableStdout, Server};
use crate::util::{loc_to_pos, NormalizedUrl}; use crate::util::{loc_to_pos, NormalizedUrl};
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> { impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
@ -11,7 +11,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: GotoImplementationParams, params: GotoImplementationParams,
) -> ELSResult<Option<GotoImplementationResponse>> { ) -> ELSResult<Option<GotoImplementationResponse>> {
_log!("implementation requested: {params:?}"); _log!(self, "implementation requested: {params:?}");
let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri); let uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri);
let pos = params.text_document_position_params.position; let pos = params.text_document_position_params.position;
let Some(symbol) = self.file_cache.get_symbol(&uri, pos) else { let Some(symbol) = self.file_cache.get_symbol(&uri, pos) else {

View file

@ -19,7 +19,7 @@ use lsp_types::{
}; };
use crate::_log; 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::abs_loc_to_lsp_loc;
use crate::util::{self, loc_to_range, NormalizedUrl}; use crate::util::{self, loc_to_range, NormalizedUrl};
@ -294,7 +294,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: InlayHintParams, params: InlayHintParams,
) -> ELSResult<Option<Vec<InlayHint>>> { ) -> ELSResult<Option<Vec<InlayHint>>> {
send_log(format!("inlay hint request: {params:?}"))?; self.send_log(format!("inlay hint request: {params:?}"))?;
let uri = NormalizedUrl::new(params.text_document.uri); let uri = NormalizedUrl::new(params.text_document.uri);
let mut result = vec![]; let mut result = vec![];
let gen = InlayHintGenerator { let gen = InlayHintGenerator {
@ -316,7 +316,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
mut hint: InlayHint, mut hint: InlayHint,
) -> ELSResult<InlayHint> { ) -> ELSResult<InlayHint> {
send_log(format!("inlay hint resolve request: {hint:?}"))?; self.send_log(format!("inlay hint resolve request: {hint:?}"))?;
if let Some(data) = &hint.data { if let Some(data) = &hint.data {
let Ok(uri) = data.as_str().unwrap().parse::<NormalizedUrl>() else { let Ok(uri) = data.as_str().unwrap().parse::<NormalizedUrl>() else {
return Ok(hint); return Ok(hint);

View file

@ -26,6 +26,6 @@ use erg_common::config::ErgConfig;
fn main() { fn main() {
let cfg = ErgConfig::default(); let cfg = ErgConfig::default();
let mut server = server::ErgLanguageServer::new(cfg); let mut server = server::ErgLanguageServer::new(cfg, None);
server.run().unwrap(); server.run().unwrap();
} }

View file

@ -1,82 +1,6 @@
use serde::{Deserialize, Serialize}; use serde::Serialize;
use serde_json::json;
use serde_json::{Number, Value};
#[derive(Debug, Serialize, Deserialize)] pub use molc::messages::ErrorMessage;
pub struct ErrorMessage {
jsonrpc: String,
id: Option<Number>,
error: Value,
}
impl ErrorMessage {
#[allow(dead_code)]
pub fn new<N: Into<Number>>(id: Option<N>, 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<S: Into<String>>(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<S: Into<String>>(message: S) -> Self {
Self {
jsonrpc: "2.0".into(),
method: "window/showMessage".into(),
params: json! {
{
"type": 3,
"message": message.into(),
}
},
}
}
pub fn error<S: Into<String>>(message: S) -> Self {
Self {
jsonrpc: "2.0".into(),
method: "window/showMessage".into(),
params: json! {
{
"type": 1,
"message": message.into(),
}
},
}
}
}
#[derive(Serialize)] #[derive(Serialize)]
pub struct LSPResult<R: Serialize> { pub struct LSPResult<R: Serialize> {

View file

@ -5,7 +5,7 @@ use erg_compiler::varinfo::AbsLocation;
use lsp_types::{Location, Position, ReferenceParams, Url}; use lsp_types::{Location, Position, ReferenceParams, Url};
use crate::_log; use crate::_log;
use crate::server::{ELSResult, Server}; use crate::server::{ELSResult, RedirectableStdout, Server};
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> { impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
@ -13,7 +13,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: ReferenceParams, params: ReferenceParams,
) -> ELSResult<Option<Vec<Location>>> { ) -> ELSResult<Option<Vec<Location>>> {
_log!("references: {params:?}"); _log!(self, "references: {params:?}");
let uri = NormalizedUrl::new(params.text_document_position.text_document.uri); let uri = NormalizedUrl::new(params.text_document_position.text_document.uri);
let pos = params.text_document_position.position; let pos = params.text_document_position.position;
let result = self.show_refs_inner(&uri, pos); let result = self.show_refs_inner(&uri, pos);

View file

@ -22,17 +22,17 @@ use lsp_types::{
WorkspaceEdit, WorkspaceEdit,
}; };
use crate::server::{send, send_error_info, send_log, ELSResult, Server}; use crate::server::{ELSResult, RedirectableStdout, Server};
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> { impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
pub(crate) fn rename(&mut self, msg: &Value) -> ELSResult<()> { pub(crate) fn rename(&mut self, msg: &Value) -> ELSResult<()> {
let params = RenameParams::deserialize(&msg["params"])?; 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 uri = NormalizedUrl::new(params.text_document_position.text_document.uri);
let pos = params.text_document_position.position; let pos = params.text_document_position.position;
if let Some(tok) = self.file_cache.get_symbol(&uri, pos) { 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 if let Some(vi) = self
.get_visitor(&uri) .get_visitor(&uri)
.and_then(|visitor| visitor.get_info(&tok)) .and_then(|visitor| visitor.get_info(&tok))
@ -65,14 +65,14 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
_ => format!("this {kind} cannot be renamed"), _ => format!("this {kind} cannot be renamed"),
}; };
let edit = WorkspaceEdit::new(changes); let edit = WorkspaceEdit::new(changes);
send( self.send_stdout(
&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": edit }), &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()); 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)) { 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() { for referrer in value.referrers.iter() {
Self::commit_change(&mut changes, referrer, params.new_name.clone()); Self::commit_change(&mut changes, referrer, params.new_name.clone());
} }
@ -83,11 +83,11 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} }
let timestamps = self.get_timestamps(changes.keys()); let timestamps = self.get_timestamps(changes.keys());
let edit = WorkspaceEdit::new(changes); let edit = WorkspaceEdit::new(changes);
send( self.send_stdout(
&json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": edit }), &json!({ "jsonrpc": "2.0", "id": msg["id"].as_i64().unwrap(), "result": edit }),
)?; )?;
for _ in 0..20 { 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(&timestamps) { if self.all_changed(&timestamps) {
break; break;
} }
@ -102,7 +102,9 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
return Ok(()); 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( fn commit_change(
@ -292,7 +294,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: RenameFilesParams, params: RenameFilesParams,
) -> ELSResult<Option<WorkspaceEdit>> { ) -> ELSResult<Option<WorkspaceEdit>> {
send_log("workspace/willRenameFiles request")?; self.send_log("workspace/willRenameFiles request")?;
let mut edits = HashMap::new(); let mut edits = HashMap::new();
let mut renames = vec![]; let mut renames = vec![];
for file in &params.files { for file in &params.files {

View file

@ -15,7 +15,7 @@ use lsp_types::{
SemanticToken, SemanticTokenType, SemanticTokens, SemanticTokensParams, SemanticTokensResult, SemanticToken, SemanticTokenType, SemanticTokens, SemanticTokensParams, SemanticTokensResult,
}; };
use crate::server::{send_log, ELSResult, Server}; use crate::server::{ELSResult, RedirectableStdout, Server};
use crate::util::{self, NormalizedUrl}; use crate::util::{self, NormalizedUrl};
#[derive(Debug)] #[derive(Debug)]
@ -288,7 +288,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: SemanticTokensParams, params: SemanticTokensParams,
) -> ELSResult<Option<SemanticTokensResult>> { ) -> ELSResult<Option<SemanticTokensResult>> {
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 uri = NormalizedUrl::new(params.text_document.uri);
let path = util::uri_to_path(&uri); let path = util::uri_to_path(&uri);
let src = self.file_cache.get_entire_code(&uri)?; let src = self.file_cache.get_entire_code(&uri)?;

View file

@ -1,6 +1,6 @@
use std::any::type_name; use std::any::type_name;
use std::io; use std::io;
use std::io::{stdin, stdout, BufRead, Read, Write}; use std::io::{stdin, BufRead, Read};
use std::ops::Not; use std::ops::Not;
use std::path::{Path, PathBuf}; use std::path::{Path, PathBuf};
use std::str::FromStr; use std::str::FromStr;
@ -26,6 +26,9 @@ use erg_compiler::lower::ASTLowerer;
use erg_compiler::module::{SharedCompilerResource, SharedModuleGraph, SharedModuleIndex}; use erg_compiler::module::{SharedCompilerResource, SharedModuleGraph, SharedModuleIndex};
use erg_compiler::ty::HasType; use erg_compiler::ty::HasType;
pub use molc::RedirectableStdout;
use molc::{DummyClient, LangServer};
use lsp_types::request::{ use lsp_types::request::{
CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare, CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare,
CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion,
@ -53,7 +56,7 @@ use crate::channels::{SendChannels, Sendable, WorkerMessage};
use crate::completion::CompletionCache; use crate::completion::CompletionCache;
use crate::file_cache::FileCache; use crate::file_cache::FileCache;
use crate::hir_visitor::{ExprKind, HIRVisitor}; 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}; use crate::util::{self, loc_to_pos, NormalizedUrl};
pub const HEALTH_CHECKER_ID: i64 = 10000; pub const HEALTH_CHECKER_ID: i64 = 10000;
@ -131,64 +134,12 @@ impl From<&str> for OptionalFeatures {
#[macro_export] #[macro_export]
macro_rules! _log { macro_rules! _log {
($($arg:tt)*) => { ($self:ident, $($arg:tt)*) => {
let s = format!($($arg)*); let s = format!($($arg)*);
$crate::server::send_log(format!("{}@{}: {s}", file!(), line!())).unwrap(); $self.send_log(format!("{}@{}: {s}", file!(), line!())).unwrap();
}; };
} }
fn send_stdout<T: ?Sized + Serialize>(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<String> {
let mut line = String::new();
stdin().lock().read_line(&mut line)?;
Ok(line)
}
fn read_exact(len: usize) -> io::Result<Vec<u8>> {
let mut buf = vec![0; len];
stdin().lock().read_exact(&mut buf)?;
Ok(buf)
}
pub(crate) fn send<T: ?Sized + Serialize>(message: &T) -> ELSResult<()> {
send_stdout(message)
}
pub(crate) fn send_log<S: Into<String>>(msg: S) -> ELSResult<()> {
if cfg!(debug_assertions) || cfg!(feature = "debug") {
send(&LogMessage::new(msg))
} else {
Ok(())
}
}
#[allow(unused)]
pub(crate) fn send_info<S: Into<String>>(msg: S) -> ELSResult<()> {
send(&ShowMessage::info(msg))
}
pub(crate) fn send_error_info<S: Into<String>>(msg: S) -> ELSResult<()> {
send(&ShowMessage::error(msg))
}
pub(crate) fn send_error<S: Into<String>>(id: Option<i64>, 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)] #[derive(Debug)]
pub struct AnalysisResult { pub struct AnalysisResult {
pub ast: Module, pub ast: Module,
@ -328,10 +279,31 @@ pub struct Server<Checker: BuildRunnable = HIRBuilder, Parser: Parsable = Simple
pub(crate) modules: ModuleCache, pub(crate) modules: ModuleCache,
pub(crate) analysis_result: AnalysisResultCache, pub(crate) analysis_result: AnalysisResultCache,
pub(crate) channels: Option<SendChannels>, pub(crate) channels: Option<SendChannels>,
pub(crate) stdout_redirect: Option<mpsc::Sender<Value>>,
pub(crate) _parser: std::marker::PhantomData<fn() -> Parser>, pub(crate) _parser: std::marker::PhantomData<fn() -> Parser>,
pub(crate) _checker: std::marker::PhantomData<fn() -> Checker>, pub(crate) _checker: std::marker::PhantomData<fn() -> Checker>,
} }
impl<C: BuildRunnable, P: Parsable> RedirectableStdout for Server<C, P> {
fn sender(&self) -> Option<&mpsc::Sender<Value>> {
self.stdout_redirect.as_ref()
}
}
impl LangServer for Server {
fn dispatch(&mut self, msg: impl Into<Value>) -> Result<(), Box<dyn std::error::Error>> {
self.dispatch(msg.into())
}
}
impl Server {
#[allow(unused)]
pub fn bind_dummy_client() -> DummyClient<Server> {
let (sender, receiver) = std::sync::mpsc::channel();
DummyClient::new(Server::new(ErgConfig::default(), Some(sender)), receiver)
}
}
impl<C: BuildRunnable, P: Parsable> Clone for Server<C, P> { impl<C: BuildRunnable, P: Parsable> Clone for Server<C, P> {
fn clone(&self) -> Self { fn clone(&self) -> Self {
Self { Self {
@ -347,6 +319,7 @@ impl<C: BuildRunnable, P: Parsable> Clone for Server<C, P> {
modules: self.modules.clone(), modules: self.modules.clone(),
analysis_result: self.analysis_result.clone(), analysis_result: self.analysis_result.clone(),
channels: self.channels.clone(), channels: self.channels.clone(),
stdout_redirect: self.stdout_redirect.clone(),
_parser: std::marker::PhantomData, _parser: std::marker::PhantomData,
_checker: std::marker::PhantomData, _checker: std::marker::PhantomData,
} }
@ -354,7 +327,7 @@ impl<C: BuildRunnable, P: Parsable> Clone for Server<C, P> {
} }
impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> { impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
pub fn new(cfg: ErgConfig) -> Self { pub fn new(cfg: ErgConfig, stdout_redirect: Option<mpsc::Sender<Value>>) -> Self {
Self { Self {
comp_cache: CompletionCache::new(cfg.copy()), comp_cache: CompletionCache::new(cfg.copy()),
cfg, cfg,
@ -364,10 +337,11 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
client_answers: Shared::new(Dict::new()), client_answers: Shared::new(Dict::new()),
disabled_features: vec![], disabled_features: vec![],
opt_features: vec![], opt_features: vec![],
file_cache: FileCache::new(), file_cache: FileCache::new(stdout_redirect.clone()),
modules: ModuleCache::new(), modules: ModuleCache::new(),
analysis_result: AnalysisResultCache::new(), analysis_result: AnalysisResultCache::new(),
channels: None, channels: None,
stdout_redirect,
_parser: std::marker::PhantomData, _parser: std::marker::PhantomData,
_checker: std::marker::PhantomData, _checker: std::marker::PhantomData,
} }
@ -377,7 +351,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
loop { loop {
let msg = self.read_message()?; let msg = self.read_message()?;
if let Err(err) = self.dispatch(msg) { if let Err(err) = self.dispatch(msg) {
send_error_info(format!("err: {err:?}"))?; self.send_error_info(format!("err: {err:?}"))?;
} }
} }
// Ok(()) // Ok(())
@ -391,12 +365,24 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} }
} }
fn read_line(&self) -> io::Result<String> {
let mut line = String::new();
stdin().lock().read_line(&mut line)?;
Ok(line)
}
fn read_exact(&self, len: usize) -> io::Result<Vec<u8>> {
let mut buf = vec![0; len];
stdin().lock().read_exact(&mut buf)?;
Ok(buf)
}
#[allow(clippy::field_reassign_with_default)] #[allow(clippy::field_reassign_with_default)]
fn init(&mut self, msg: &Value, id: i64) -> ELSResult<()> { 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() { if msg.get("params").is_some() && msg["params"].get("capabilities").is_some() {
self.init_params = InitializeParams::deserialize(&msg["params"])?; 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(); let mut args = self.cfg.runtime_args.iter();
while let Some(&arg) = args.next() { while let Some(&arg) = args.next() {
@ -413,7 +399,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
let mut result = InitializeResult::default(); let mut result = InitializeResult::default();
result.capabilities = self.init_capabilities(); result.capabilities = self.init_capabilities();
self.init_services(); self.init_services();
send(&json!({ self.send_stdout(&json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"id": id, "id": id,
"result": result, "result": result,
@ -520,7 +506,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
section: Some("files.autoSave".to_string()), section: Some("files.autoSave".to_string()),
}], }],
}; };
send(&json!({ self.send_stdout(&json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"id": ASK_AUTO_SAVE_ID, "id": ASK_AUTO_SAVE_ID,
"method": "workspace/configuration", "method": "workspace/configuration",
@ -606,13 +592,13 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} }
fn exit(&self) -> ELSResult<()> { fn exit(&self) -> ELSResult<()> {
send_log("exiting ELS")?; self.send_log("exiting ELS")?;
std::process::exit(0); std::process::exit(0);
} }
fn shutdown(&self, id: i64) -> ELSResult<()> { fn shutdown(&self, id: i64) -> ELSResult<()> {
send_log("shutting down ELS")?; self.send_log("shutting down ELS")?;
send(&json!({ self.send_stdout(&json!({
"jsonrpc": "2.0", "jsonrpc": "2.0",
"id": id, "id": id,
"result": json!(null), "result": json!(null),
@ -634,7 +620,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
// Read in the "Content-Length: xx" part. // Read in the "Content-Length: xx" part.
let mut size: Option<usize> = None; let mut size: Option<usize> = None;
loop { loop {
let buffer = read_line()?; let buffer = self.read_line()?;
// End of input. // End of input.
if buffer.is_empty() { if buffer.is_empty() {
@ -689,7 +675,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} }
}; };
let content = read_exact(size)?; let content = self.read_exact(size)?;
let s = String::from_utf8(content) let s = String::from_utf8(content)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?;
@ -704,7 +690,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
(Some(id), Some(method)) => self.handle_request(&msg, id, method), (Some(id), Some(method)) => self.handle_request(&msg, id, method),
(Some(id), None) => self.handle_response(id, &msg), (Some(id), None) => self.handle_response(id, &msg),
(None, Some(notification)) => self.handle_notification(&msg, notification), (None, Some(notification)) => self.handle_notification(&msg, notification),
_ => send_invalid_req_error(), _ => self.send_invalid_req_error(),
} }
} }
@ -735,10 +721,10 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
match msg { match msg {
WorkerMessage::Request(id, params) => match handler(&mut _self, params) { WorkerMessage::Request(id, params) => match handler(&mut _self, params) {
Ok(result) => { Ok(result) => {
let _ = send(&LSPResult::new(id, result)); let _ = _self.send_stdout(&LSPResult::new(id, result));
} }
Err(err) => { Err(err) => {
let _ = send(&ErrorMessage::new( let _ = _self.send_stdout(&ErrorMessage::new(
Some(id), Some(id),
format!("err from {}: {err}", type_name::<R>()).into(), format!("err from {}: {err}", type_name::<R>()).into(),
)); ));
@ -787,7 +773,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
} }
CallHierarchyPrepare::METHOD => self.parse_send::<CallHierarchyPrepare>(id, msg), CallHierarchyPrepare::METHOD => self.parse_send::<CallHierarchyPrepare>(id, msg),
FoldingRangeRequest::METHOD => self.parse_send::<FoldingRangeRequest>(id, msg), FoldingRangeRequest::METHOD => self.parse_send::<FoldingRangeRequest>(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<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
match method { match method {
"initialized" => { "initialized" => {
self.ask_auto_save()?; self.ask_auto_save()?;
send_log("successfully bound") self.send_log("successfully bound")
} }
"exit" => self.exit(), "exit" => self.exit(),
"textDocument/didOpen" => { "textDocument/didOpen" => {
let params = DidOpenTextDocumentParams::deserialize(msg["params"].clone())?; let params = DidOpenTextDocumentParams::deserialize(msg["params"].clone())?;
let uri = NormalizedUrl::new(params.text_document.uri); 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 code = params.text_document.text;
let ver = params.text_document.version; let ver = params.text_document.version;
self.file_cache.update(&uri, code.clone(), Some(ver)); self.file_cache.update(&uri, code.clone(), Some(ver));
@ -810,7 +796,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
"textDocument/didSave" => { "textDocument/didSave" => {
let uri = let uri =
NormalizedUrl::parse(msg["params"]["textDocument"]["uri"].as_str().unwrap())?; 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)?; let code = self.file_cache.get_entire_code(&uri)?;
self.clear_cache(&uri); self.clear_cache(&uri);
self.check_file(uri, code) self.check_file(uri, code)
@ -831,7 +817,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
self.file_cache.incremental_update(params); self.file_cache.incremental_update(params);
Ok(()) Ok(())
} }
_ => send_log(format!("received notification: {method}")), _ => self.send_log(format!("received notification: {method}")),
} }
} }
@ -845,7 +831,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
.send(WorkerMessage::Request(0, ()))?; .send(WorkerMessage::Request(0, ()))?;
} }
_ => { _ => {
_log!("msg: {msg}"); _log!(self, "msg: {msg}");
if msg.get("error").is_none() { if msg.get("error").is_none() {
self.client_answers.borrow_mut().insert(id, msg.clone()); self.client_answers.borrow_mut().insert(id, msg.clone());
} }
@ -947,7 +933,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
.file_cache .file_cache
.get_token_relatively(uri, attr_marker_pos, -2); .get_token_relatively(uri, attr_marker_pos, -2);
if let Some(token) = maybe_token { if let Some(token) = maybe_token {
// send_log(format!("token: {token}"))?; // self.send_log(format!("token: {token}"))?;
let mut ctxs = vec![]; let mut ctxs = vec![];
if let Some(visitor) = self.get_visitor(uri) { if let Some(visitor) = self.get_visitor(uri) {
if let Some(expr) = if let Some(expr) =
@ -965,12 +951,12 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
ctxs.extend(singular_ctxs); ctxs.extend(singular_ctxs);
} }
} else { } else {
_log!("expr not found: {token}"); _log!(self, "expr not found: {token}");
} }
} }
Ok(ctxs) Ok(ctxs)
} else { } else {
send_log("token not found")?; self.send_log("token not found")?;
Ok(vec![]) Ok(vec![])
} }
} }

View file

@ -11,7 +11,7 @@ use lsp_types::{
}; };
use crate::hir_visitor::GetExprKind; 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}; use crate::util::{loc_to_pos, pos_to_loc, NormalizedUrl};
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -52,7 +52,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: SignatureHelpParams, params: SignatureHelpParams,
) -> ELSResult<Option<SignatureHelp>> { ) -> ELSResult<Option<SignatureHelp>> {
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 uri = NormalizedUrl::new(params.text_document_position_params.text_document.uri);
let pos = params.text_document_position_params.position; let pos = params.text_document_position_params.position;
if params.context.as_ref().map(|ctx| &ctx.trigger_kind) if params.context.as_ref().map(|ctx| &ctx.trigger_kind)
@ -83,7 +83,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
offset: isize, offset: isize,
) -> Option<(Token, Expr)> { ) -> Option<(Token, Expr)> {
let token = self.file_cache.get_token_relatively(uri, pos, offset)?; 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(visitor) = self.get_visitor(uri) {
if let Some(expr) = visitor.get_min_expr(loc_to_pos(token.loc())?) { if let Some(expr) = visitor.get_min_expr(loc_to_pos(token.loc())?) {
return Some((token, expr.clone())); return Some((token, expr.clone()));
@ -140,7 +140,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
ctx: &SignatureHelpContext, ctx: &SignatureHelpContext,
) -> Option<SignatureHelp> { ) -> Option<SignatureHelp> {
if let Some(token) = self.file_cache.get_token(uri, pos) { 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::<Call>(uri, pos) { if let Some(call) = self.get_min::<Call>(uri, pos) {
if call.ln_begin() > token.ln_begin() || call.ln_end() < token.ln_end() { if call.ln_begin() > token.ln_begin() || call.ln_end() < token.ln_end() {
return None; return None;
@ -148,10 +148,10 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
let nth = self.nth(uri, &call, pos) as u32; let nth = self.nth(uri, &call, pos) as u32;
return self.make_sig_help(call.obj.as_ref(), nth); return self.make_sig_help(call.obj.as_ref(), nth);
} else { } else {
crate::_log!("failed to get the call"); crate::_log!(self, "failed to get the call");
} }
} else { } else {
crate::_log!("failed to get the token"); crate::_log!(self, "failed to get the token");
} }
ctx.active_signature_help.clone() ctx.active_signature_help.clone()
} }
@ -160,7 +160,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
if let Some((_token, Expr::Accessor(acc))) = self.get_min_expr(uri, pos, -2) { if let Some((_token, Expr::Accessor(acc))) = self.get_min_expr(uri, pos, -2) {
return self.make_sig_help(&acc, 0); return self.make_sig_help(&acc, 0);
} else { } else {
crate::_log!("lex error occurred"); crate::_log!(self, "lex error occurred");
} }
None None
} }
@ -171,7 +171,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
let help = self.make_sig_help(call.obj.as_ref(), nth); let help = self.make_sig_help(call.obj.as_ref(), nth);
return help; return help;
} else { } else {
crate::_log!("failed to get continuous help"); crate::_log!(self, "failed to get continuous help");
} }
None None
} }

View file

@ -12,7 +12,7 @@ use lsp_types::{
}; };
use crate::_log; 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}; use crate::util::{abs_loc_to_lsp_loc, loc_to_range, NormalizedUrl};
pub(crate) fn symbol_kind(vi: &VarInfo) -> SymbolKind { pub(crate) fn symbol_kind(vi: &VarInfo) -> SymbolKind {
@ -35,7 +35,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: WorkspaceSymbolParams, params: WorkspaceSymbolParams,
) -> ELSResult<Option<Vec<SymbolInformation>>> { ) -> ELSResult<Option<Vec<SymbolInformation>>> {
_log!("workspace symbol requested: {params:?}"); _log!(self, "workspace symbol requested: {params:?}");
let mut res = vec![]; let mut res = vec![];
for module in self.modules.values() { for module in self.modules.values() {
for (name, vi) in module.context.local_dir() { for (name, vi) in module.context.local_dir() {
@ -74,7 +74,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
&mut self, &mut self,
params: DocumentSymbolParams, params: DocumentSymbolParams,
) -> ELSResult<Option<DocumentSymbolResponse>> { ) -> ELSResult<Option<DocumentSymbolResponse>> {
_log!("document symbol requested: {params:?}"); _log!(self, "document symbol requested: {params:?}");
let uri = NormalizedUrl::new(params.text_document.uri); let uri = NormalizedUrl::new(params.text_document.uri);
if let Some(result) = self.analysis_result.get(&uri) { if let Some(result) = self.analysis_result.get(&uri) {
if let Some(hir) = &result.artifact.object { if let Some(hir) = &result.artifact.object {

View file

@ -1,428 +1,38 @@
use std::fs::File;
use std::io::Read;
use std::path::Path; use std::path::Path;
use lsp_types::{ use lsp_types::{
CompletionContext, CompletionParams, CompletionResponse, CompletionTriggerKind, CompletionResponse, DocumentSymbolResponse, FoldingRange, FoldingRangeKind,
ConfigurationParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams, GotoDefinitionResponse, HoverContents, MarkedString,
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,
}; };
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_A: &str = "tests/a.er";
const FILE_B: &str = "tests/b.er"; const FILE_B: &str = "tests/b.er";
const FILE_IMPORTS: &str = "tests/imports.er"; const FILE_IMPORTS: &str = "tests/imports.er";
fn add_char(line: u32, character: u32, text: &str) -> TextDocumentContentChangeEvent { use els::{NormalizedUrl, Server};
TextDocumentContentChangeEvent { use molc::{add_char, oneline_range};
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<Value> {
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::<usize>().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<String, Box<dyn std::error::Error>> {
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<R>(&mut self) -> Result<R, Box<dyn std::error::Error>>
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<String, Box<dyn std::error::Error>> {
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<String, Box<dyn std::error::Error>> {
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<String, Box<dyn std::error::Error>> {
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<CompletionResponse, Box<dyn std::error::Error>> {
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::<CompletionResponse>()
}
fn request_rename(
&mut self,
uri: Url,
line: u32,
col: u32,
new_name: &str,
) -> Result<WorkspaceEdit, Box<dyn std::error::Error>> {
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::<WorkspaceEdit>()
}
fn request_signature_help(
&mut self,
uri: Url,
line: u32,
col: u32,
character: &str,
) -> Result<SignatureHelp, Box<dyn std::error::Error>> {
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::<SignatureHelp>()
}
fn request_hover(
&mut self,
uri: Url,
line: u32,
col: u32,
) -> Result<Hover, Box<dyn std::error::Error>> {
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::<Hover>()
}
fn request_references(
&mut self,
uri: Url,
line: u32,
col: u32,
) -> Result<Vec<Location>, Box<dyn std::error::Error>> {
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::<Vec<Location>>()
}
fn request_goto_definition(
&mut self,
uri: Url,
line: u32,
col: u32,
) -> Result<Location, Box<dyn std::error::Error>> {
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::<Location>()
}
fn request_folding_range(
&mut self,
uri: Url,
) -> Result<Option<Vec<FoldingRange>>, Box<dyn std::error::Error>> {
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::<Option<Vec<FoldingRange>>>()
}
fn request_document_symbols(
&mut self,
uri: Url,
) -> Result<Option<DocumentSymbolResponse>, Box<dyn std::error::Error>> {
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::<Option<DocumentSymbolResponse>>()
}
}
#[test] #[test]
fn test_open() -> Result<(), Box<dyn std::error::Error>> { fn test_open() -> Result<(), Box<dyn std::error::Error>> {
let mut client = DummyClient::new(); let mut client = Server::bind_dummy_client();
client.request_initialize()?; client.request_initialize()?;
let result = client.notify_open(FILE_A)?; client.notify_open(FILE_A)?;
assert!(result.contains("tests/a.er passed, found warns: 0")); client.wait_messages(3)?;
assert!(client.responses.iter().any(|val| val
.to_string()
.contains("tests/a.er passed, found warns: 0")));
Ok(()) Ok(())
} }
#[test] #[test]
fn test_completion() -> Result<(), Box<dyn std::error::Error>> { fn test_completion() -> Result<(), Box<dyn std::error::Error>> {
let mut client = DummyClient::new(); let mut client = Server::bind_dummy_client();
client.request_initialize()?; client.request_initialize()?;
let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?;
client.notify_open(FILE_A)?; 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, 0, "x"))?;
client.notify_change(uri.clone().raw(), add_char(2, 1, "."))?; client.notify_change(uri.clone().raw(), add_char(2, 1, "."))?;
let resp = client.request_completion(uri.raw(), 2, 2, ".")?; 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.len() >= 40);
assert!(items.iter().any(|item| item.label == "abs")); assert!(items.iter().any(|item| item.label == "abs"));
Ok(()) Ok(())
@ -433,13 +43,13 @@ fn test_completion() -> Result<(), Box<dyn std::error::Error>> {
#[test] #[test]
fn test_neighbor_completion() -> Result<(), Box<dyn std::error::Error>> { fn test_neighbor_completion() -> Result<(), Box<dyn std::error::Error>> {
let mut client = DummyClient::new(); let mut client = Server::bind_dummy_client();
client.request_initialize()?; client.request_initialize()?;
let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?;
client.notify_open(FILE_A)?; client.notify_open(FILE_A)?;
client.notify_open(FILE_B)?; client.notify_open(FILE_B)?;
let resp = client.request_completion(uri.raw(), 2, 0, "n")?; 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.len() >= 40);
assert!(items assert!(items
.iter() .iter()
@ -452,11 +62,13 @@ fn test_neighbor_completion() -> Result<(), Box<dyn std::error::Error>> {
#[test] #[test]
fn test_rename() -> Result<(), Box<dyn std::error::Error>> { fn test_rename() -> Result<(), Box<dyn std::error::Error>> {
let mut client = DummyClient::new(); let mut client = Server::bind_dummy_client();
client.request_initialize()?; client.request_initialize()?;
let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?;
client.notify_open(FILE_A)?; 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 assert!(edit
.changes .changes
.is_some_and(|changes| changes.values().next().unwrap().len() == 2)); .is_some_and(|changes| changes.values().next().unwrap().len() == 2));
@ -465,13 +77,15 @@ fn test_rename() -> Result<(), Box<dyn std::error::Error>> {
#[test] #[test]
fn test_signature_help() -> Result<(), Box<dyn std::error::Error>> { fn test_signature_help() -> Result<(), Box<dyn std::error::Error>> {
let mut client = DummyClient::new(); let mut client = Server::bind_dummy_client();
client.request_initialize()?; client.request_initialize()?;
let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?;
client.notify_open(FILE_A)?; 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, 0, "assert"))?;
client.notify_change(uri.clone().raw(), add_char(2, 6, "("))?; 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); assert_eq!(help.signatures.len(), 1);
let sig = &help.signatures[0]; let sig = &help.signatures[0];
assert_eq!(sig.label, "::assert: (test: Bool, msg := Str) -> NoneType"); assert_eq!(sig.label, "::assert: (test: Bool, msg := Str) -> NoneType");
@ -481,11 +95,11 @@ fn test_signature_help() -> Result<(), Box<dyn std::error::Error>> {
#[test] #[test]
fn test_hover() -> Result<(), Box<dyn std::error::Error>> { fn test_hover() -> Result<(), Box<dyn std::error::Error>> {
let mut client = DummyClient::new(); let mut client = Server::bind_dummy_client();
client.request_initialize()?; client.request_initialize()?;
let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?;
client.notify_open(FILE_A)?; 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 { let HoverContents::Array(contents) = hover.contents else {
todo!() todo!()
}; };
@ -506,30 +120,34 @@ fn test_hover() -> Result<(), Box<dyn std::error::Error>> {
#[test] #[test]
fn test_references() -> Result<(), Box<dyn std::error::Error>> { fn test_references() -> Result<(), Box<dyn std::error::Error>> {
let mut client = DummyClient::new(); let mut client = Server::bind_dummy_client();
client.request_initialize()?; client.request_initialize()?;
let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?;
client.notify_open(FILE_A)?; 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.len(), 1);
assert_eq!(&locations[0].range, &single_range(1, 4, 5)); assert_eq!(&locations[0].range, &oneline_range(1, 4, 5));
Ok(()) Ok(())
} }
#[test] #[test]
fn test_goto_definition() -> Result<(), Box<dyn std::error::Error>> { fn test_goto_definition() -> Result<(), Box<dyn std::error::Error>> {
let mut client = DummyClient::new(); let mut client = Server::bind_dummy_client();
client.request_initialize()?; client.request_initialize()?;
let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?;
client.notify_open(FILE_A)?; client.notify_open(FILE_A)?;
let location = client.request_goto_definition(uri.raw(), 1, 4)?; let Some(GotoDefinitionResponse::Scalar(location)) =
assert_eq!(&location.range, &single_range(0, 0, 1)); client.request_goto_definition(uri.raw(), 1, 4)?
else {
todo!()
};
assert_eq!(&location.range, &oneline_range(0, 0, 1));
Ok(()) Ok(())
} }
#[test] #[test]
fn test_folding_range() -> Result<(), Box<dyn std::error::Error>> { fn test_folding_range() -> Result<(), Box<dyn std::error::Error>> {
let mut client = DummyClient::new(); let mut client = Server::bind_dummy_client();
client.request_initialize()?; client.request_initialize()?;
let uri = NormalizedUrl::from_file_path(Path::new(FILE_IMPORTS).canonicalize()?)?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_IMPORTS).canonicalize()?)?;
client.notify_open(FILE_IMPORTS)?; client.notify_open(FILE_IMPORTS)?;
@ -550,7 +168,7 @@ fn test_folding_range() -> Result<(), Box<dyn std::error::Error>> {
#[test] #[test]
fn test_document_symbol() -> Result<(), Box<dyn std::error::Error>> { fn test_document_symbol() -> Result<(), Box<dyn std::error::Error>> {
let mut client = DummyClient::new(); let mut client = Server::bind_dummy_client();
client.request_initialize()?; client.request_initialize()?;
let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?; let uri = NormalizedUrl::from_file_path(Path::new(FILE_A).canonicalize()?)?;
client.notify_open(FILE_A)?; client.notify_open(FILE_A)?;

19
crates/molc/Cargo.toml Normal file
View file

@ -0,0 +1,19 @@
[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"

69
crates/molc/README.md Normal file
View file

@ -0,0 +1,69 @@
# `molc`
`molc` is a mock 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::{DummyClient, LangServer, RedirectableStdout};
use molc::oneline_range;
pub struct Server {
stdout_redirect: Option<std::sync::mpsc::Sender<Value>>,
...
}
impl LangServer for Server {
fn dispatch(&mut self, msg: impl Into<Value>) -> Result<(), Box<dyn std::error::Error>> {
self.dispatch(msg)
}
}
impl RedirectableStdout for Server {
fn sender(&self) -> Option<&std::sync::mpsc::Sender<Value>> {
self.stdout_redirect.as_ref()
}
}
impl Server {
fn bind_dummy_client() -> DummyClient<Self> {
// 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<dyn std::error::Error>> {
let mut client = Server::bind_dummy_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(())
}
```

466
crates/molc/src/lib.rs Normal file
View file

@ -0,0 +1,466 @@
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<Value> {
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::<usize>().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<T> = std::result::Result<T, Box<dyn std::error::Error>>;
pub trait RedirectableStdout {
fn sender(&self) -> Option<&Sender<Value>>;
fn send_stdout<T: ?Sized + Serialize>(&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<S: Into<String>>(&self, msg: S) -> Result<()> {
if cfg!(debug_assertions) || cfg!(feature = "debug") {
self.send_stdout(&LogMessage::new(msg))
} else {
Ok(())
}
}
#[allow(unused)]
fn send_info<S: Into<String>>(&self, msg: S) -> Result<()> {
self.send_stdout(&ShowMessage::info(msg))
}
fn send_error_info<S: Into<String>>(&self, msg: S) -> Result<()> {
self.send_stdout(&ShowMessage::error(msg))
}
fn send_error<S: Into<String>>(&self, id: Option<i64>, 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<Value>) -> Result<()>;
}
pub struct DummyClient<LS: LangServer> {
server: LS,
receiver: std::sync::mpsc::Receiver<Value>,
server_capas: Option<ServerCapabilities>,
pub responses: Vec<Value>,
#[allow(clippy::complexity)]
request_handlers: HashMap<String, Box<dyn Fn(&Value, &mut LS) -> Result<()>>>,
ver: i32,
req_id: i64,
}
impl<LS: LangServer> DummyClient<LS> {
/// The server should send responses to the channel at least during testing.
pub fn new(server: LS, receiver: std::sync::mpsc::Receiver<Value>) -> Self {
DummyClient {
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<String>,
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<R>(&mut self) -> Result<R>
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<InitializeResult> {
let msg = json!({
"jsonrpc": "2.0",
"id": self.req_id,
"method": "initialize",
});
self.server.dispatch(msg)?;
let res = self.wait_for::<InitializeResult>()?;
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<Option<CompletionResponse>> {
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::<Option<CompletionResponse>>()
}
pub fn request_rename(
&mut self,
uri: Url,
line: u32,
col: u32,
new_name: &str,
) -> Result<Option<WorkspaceEdit>> {
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::<Option<WorkspaceEdit>>()
}
pub fn request_signature_help(
&mut self,
uri: Url,
line: u32,
col: u32,
character: &str,
) -> Result<Option<SignatureHelp>> {
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::<Option<SignatureHelp>>()
}
pub fn request_hover(&mut self, uri: Url, line: u32, col: u32) -> Result<Option<Hover>> {
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::<Option<Hover>>()
}
pub fn request_references(
&mut self,
uri: Url,
line: u32,
col: u32,
) -> Result<Option<Vec<Location>>> {
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::<Option<Vec<Location>>>()
}
pub fn request_goto_definition(
&mut self,
uri: Url,
line: u32,
col: u32,
) -> Result<Option<GotoDefinitionResponse>> {
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::<Option<GotoDefinitionResponse>>()
}
pub fn request_folding_range(&mut self, uri: Url) -> Result<Option<Vec<FoldingRange>>> {
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::<Option<Vec<FoldingRange>>>()
}
pub fn request_document_symbols(&mut self, uri: Url) -> Result<Option<DocumentSymbolResponse>> {
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::<Option<DocumentSymbolResponse>>()
}
}

View file

@ -0,0 +1,79 @@
use serde::{Deserialize, Serialize};
use serde_json::json;
use serde_json::{Number, Value};
#[derive(Debug, Serialize, Deserialize)]
pub struct ErrorMessage {
jsonrpc: String,
id: Option<Number>,
error: Value,
}
impl ErrorMessage {
#[allow(dead_code)]
pub fn new<N: Into<Number>>(id: Option<N>, 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<S: Into<String>>(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<S: Into<String>>(message: S) -> Self {
Self {
jsonrpc: "2.0".into(),
method: "window/showMessage".into(),
params: json! {
{
"type": 3,
"message": message.into(),
}
},
}
}
pub fn error<S: Into<String>>(message: S) -> Self {
Self {
jsonrpc: "2.0".into(),
method: "window/showMessage".into(),
params: json! {
{
"type": 1,
"message": message.into(),
}
},
}
}
}

View file

@ -34,7 +34,7 @@ fn run() {
#[cfg(feature = "els")] #[cfg(feature = "els")]
{ {
use els::ErgLanguageServer; use els::ErgLanguageServer;
let mut server = ErgLanguageServer::new(cfg); let mut server = ErgLanguageServer::new(cfg, None);
server.run().unwrap(); server.run().unwrap();
ExitStatus::OK ExitStatus::OK
} }