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