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