use std::any::type_name; use std::io; use std::io::{stdin, BufRead, Read}; use std::ops::Not; use std::panic; use std::path::PathBuf; use std::str::FromStr; use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::{mpsc, Arc}; use std::time::Duration; use erg_common::config::{ErgConfig, ErgMode}; use erg_common::consts::PYTHON_MODE; use erg_common::dict::Dict; use erg_common::env::erg_path; use erg_common::pathutil::{project_entry_dir_of, NormalizedPathBuf}; use erg_common::set::Set; use erg_common::shared::{MappedRwLockReadGuard, Shared}; use erg_common::spawn::{safe_yield, spawn_new_thread}; use erg_common::traits::Stream; use erg_common::vfs::VFS; use erg_common::{fn_name, lsp_log, normalize_path}; use erg_compiler::artifact::BuildRunnable; use erg_compiler::build_package::PackageBuilder; use erg_compiler::context::{Context, ModuleContext}; use erg_compiler::erg_parser::ast::Module; use erg_compiler::erg_parser::parse::{Parsable, SimpleParser}; use erg_compiler::error::CompileWarning; use erg_compiler::hir::{Expr, HIR}; use erg_compiler::lower::ASTLowerer; use erg_compiler::module::{IRs, ModuleEntry, SharedCompilerResource}; use erg_compiler::ty::{HasType, Type}; pub use molc::RedirectableStdout; use molc::{FakeClient, LangServer}; use lsp_types::request::{ CallHierarchyIncomingCalls, CallHierarchyOutgoingCalls, CallHierarchyPrepare, CodeActionRequest, CodeActionResolveRequest, CodeLensRequest, Completion, DocumentHighlightRequest, DocumentLinkRequest, DocumentSymbolRequest, ExecuteCommand, FoldingRangeRequest, GotoDefinition, GotoImplementation, GotoTypeDefinition, HoverRequest, InlayHintRequest, InlayHintResolveRequest, References, Rename, Request, ResolveCompletionItem, SelectionRangeRequest, SemanticTokensFullRequest, SignatureHelpRequest, WillRenameFiles, WorkspaceSymbol, }; use lsp_types::{ CallHierarchyServerCapability, CodeActionKind, CodeActionOptions, CodeActionProviderCapability, CodeLensOptions, CompletionOptions, ConfigurationItem, ConfigurationParams, DidChangeTextDocumentParams, DidOpenTextDocumentParams, DidSaveTextDocumentParams, DocumentLinkOptions, ExecuteCommandOptions, FoldingRangeProviderCapability, HoverProviderCapability, ImplementationProviderCapability, InitializeParams, InitializeResult, InlayHintOptions, InlayHintServerCapabilities, NumberOrString, OneOf, Position, ProgressParams, ProgressParamsValue, SelectionRangeProviderCapability, SemanticTokenModifier, SemanticTokenType, SemanticTokensFullOptions, SemanticTokensLegend, SemanticTokensOptions, SemanticTokensServerCapabilities, ServerCapabilities, SignatureHelpOptions, TypeDefinitionProviderCapability, WorkDoneProgress, WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd, WorkDoneProgressOptions, }; use serde::{Deserialize, Serialize}; use serde_json::json; use serde_json::Value; use crate::channels::{SendChannels, Sendable, WorkerMessage}; use crate::completion::CompletionCache; use crate::file_cache::FileCache; use crate::hir_visitor::{ExprKind, HIRVisitor}; use crate::message::{ErrorMessage, LSPResult}; use crate::scheduler::Scheduler; use crate::util::{self, loc_to_pos, NormalizedUrl}; pub const HEALTH_CHECKER_ID: i64 = 10000; pub const ASK_AUTO_SAVE_ID: i64 = 10001; pub type ELSResult = Result>; pub type ErgLanguageServer = Server; pub type Handler = fn(&mut Server, Params) -> ELSResult; #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum DefaultFeatures { /* LSP features */ CodeAction, CodeLens, Completion, Diagnostics, FindReferences, GotoDefinition, GotoTypeDefinition, Hover, InlayHint, Rename, SemanticTokens, SignatureHelp, DocumentHighlight, DocumentLink, FoldingRange, SelectionRange, /* ELS specific features */ SmartCompletion, DeepCompletion, } impl From<&str> for DefaultFeatures { fn from(s: &str) -> Self { match s { "codeaction" | "codeAction" | "code-action" => DefaultFeatures::CodeAction, "codelens" | "codeLens" | "code-lens" => DefaultFeatures::CodeLens, "completion" => DefaultFeatures::Completion, "diagnostic" | "diagnostics" => DefaultFeatures::Diagnostics, "hover" => DefaultFeatures::Hover, "semantictoken" | "semantictokens" | "semanticToken" | "semanticTokens" | "semantic-tokens" => DefaultFeatures::SemanticTokens, "rename" => DefaultFeatures::Rename, "inlayhint" | "inlayhints" | "inlayHint" | "inlayHints" | "inlay-hint" | "inlay-hints" => DefaultFeatures::InlayHint, "findreferences" | "findReferences" | "find-references" | "references" => { DefaultFeatures::FindReferences } "gotodefinition" | "gotoDefinition" | "goto-definition" => { DefaultFeatures::GotoDefinition } "gototypedefinition" | "gotoTypeDefinition" | "goto-type-definition" => { DefaultFeatures::GotoTypeDefinition } "signaturehelp" | "signatureHelp" | "signature-help" | "code-signature" => { DefaultFeatures::SignatureHelp } "documenthighlight" | "documentHighlight" | "document-highlight" => { DefaultFeatures::DocumentHighlight } "documentlink" | "documentLink" | "document-link" => DefaultFeatures::DocumentLink, "foldingrange" | "foldingRange" | "folding-range" => DefaultFeatures::FoldingRange, "selectionrange" | "selectionRange" | "selection-range" => { DefaultFeatures::SelectionRange } "smartcompletion" | "smartCompletion" | "smart-completion" => { DefaultFeatures::SmartCompletion } "deepcompletion" | "deepCompletion" | "deep-completion" => { DefaultFeatures::DeepCompletion } _ => panic!("unknown feature: {s}"), } } } #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] pub enum OptionalFeatures { CheckOnType, Lint, } impl From<&str> for OptionalFeatures { fn from(s: &str) -> Self { match s { "checkontype" | "checkOnType" | "check-on-type" => OptionalFeatures::CheckOnType, "lint" | "linting" => OptionalFeatures::Lint, _ => panic!("unknown feature: {s}"), } } } #[macro_export] macro_rules! _log { ($self:ident, $($arg:tt)*) => {{ let s = format!($($arg)*); let _ = $self.send_log(format!("{}@{}: {s}", file!(), line!())); }}; } pub const TRIGGER_CHARS: [&str; 4] = [".", ":", "(", " "]; #[derive(Debug, Clone, Default)] pub struct Flags { pub(crate) client_initialized: Arc, pub(crate) workspace_checked: Arc, pub(crate) builtin_modules_loaded: Arc, } impl Flags { pub fn client_initialized(&self) -> bool { self.client_initialized.load(Ordering::Relaxed) } pub fn workspace_checked(&self) -> bool { self.workspace_checked.load(Ordering::Relaxed) } #[allow(unused)] pub fn builtin_modules_loaded(&self) -> bool { self.builtin_modules_loaded.load(Ordering::Relaxed) } } /// A Language Server, which can be used any object implementing `BuildRunnable` internally by passing it as a generic parameter. #[derive(Debug)] pub struct Server { pub(crate) cfg: ErgConfig, pub(crate) home: PathBuf, pub(crate) erg_path: PathBuf, pub(crate) init_params: InitializeParams, pub(crate) client_answers: Shared>, pub(crate) disabled_features: Vec, pub(crate) opt_features: Vec, pub(crate) file_cache: FileCache, pub(crate) comp_cache: CompletionCache, pub flags: Flags, pub(crate) shared: SharedCompilerResource, pub(crate) channels: Option, pub(crate) stdout_redirect: Option>, pub(crate) scheduler: Scheduler, pub(crate) _parser: std::marker::PhantomData Parser>, pub(crate) _checker: std::marker::PhantomData Checker>, } impl RedirectableStdout for Server { fn sender(&self) -> Option<&mpsc::Sender> { self.stdout_redirect.as_ref() } } impl LangServer for Server { fn dispatch(&mut self, msg: impl Into) -> Result<(), Box> { self.dispatch(msg.into()) } } impl Server { #[allow(unused)] pub fn bind_fake_client() -> FakeClient { let (sender, receiver) = std::sync::mpsc::channel(); let cfg = ErgConfig { mode: ErgMode::LanguageServer, ..Default::default() }; FakeClient::new(Server::new(cfg, Some(sender)), receiver) } } impl Clone for Server { fn clone(&self) -> Self { Self { cfg: self.cfg.clone(), home: self.home.clone(), erg_path: self.erg_path.clone(), init_params: self.init_params.clone(), client_answers: self.client_answers.clone(), disabled_features: self.disabled_features.clone(), opt_features: self.opt_features.clone(), file_cache: self.file_cache.clone(), comp_cache: self.comp_cache.clone(), shared: self.shared.clone(), channels: self.channels.clone(), flags: self.flags.clone(), stdout_redirect: self.stdout_redirect.clone(), scheduler: self.scheduler.clone(), _parser: std::marker::PhantomData, _checker: std::marker::PhantomData, } } } impl Server { pub fn new(cfg: ErgConfig, stdout_redirect: Option>) -> Self { let default_hook = panic::take_hook(); panic::set_hook(Box::new(move |panic_info| { lsp_log!("{panic_info}"); default_hook(panic_info); })); let flags = Flags::default(); let cfg = Self::register_packages(cfg); let shared = SharedCompilerResource::new(cfg.copy()); let mut args = cfg.runtime_args.iter(); let mut disabled_features = vec![]; let mut opt_features = vec![]; while let Some(&arg) = args.next() { if arg == "--disable" { if let Some(&feature) = args.next() { disabled_features.push(DefaultFeatures::from(feature)); } } else if arg == "--enable" { if let Some(&feature) = args.next() { opt_features.push(OptionalFeatures::from(feature)); } } } let external_items = !disabled_features.contains(&DefaultFeatures::DeepCompletion); Self { comp_cache: CompletionCache::new( cfg.copy(), flags.clone(), shared.clone(), external_items, ), shared, cfg, home: normalize_path(std::env::current_dir().unwrap_or_default()), erg_path: erg_path().clone(), // already normalized init_params: InitializeParams::default(), client_answers: Shared::new(Dict::new()), disabled_features, opt_features, file_cache: FileCache::new(stdout_redirect.clone()), channels: None, flags, stdout_redirect, scheduler: Scheduler::new(), _parser: std::marker::PhantomData, _checker: std::marker::PhantomData, } } fn register_packages(mut cfg: ErgConfig) -> ErgConfig { use erg_compiler::erg_parser::ast; let home = normalize_path(std::env::current_dir().unwrap_or_default()); let Ok(src) = VFS.read(home.join("package.lock.er")) else { lsp_log!("package.lock.er not found"); return cfg; }; let Ok(artifact) = ::parse(src) else { lsp_log!("failed to parse package.lock.er"); return cfg; }; let Some(pkgs) = artifact.ast.get_attr("packages") else { lsp_log!("failed to get packages: {}", artifact.ast); return cfg; }; let Some(ast::Expr::List(ast::List::Normal(lis))) = pkgs.body.block.first() else { lsp_log!("packages must be an array: {pkgs}"); return cfg; }; let mut packages = vec![]; for rec in lis.iter() { let ast::Expr::Record(rec) = rec else { lsp_log!("packages must be records: {rec}"); break; }; let Some(ast::Expr::Literal(name)) = rec.get("name").and_then(|name| name.body.block.first()) else { break; }; let name = name.token.content.replace('\"', ""); let Some(ast::Expr::Literal(as_name)) = rec .get("as_name") .and_then(|as_name| as_name.body.block.first()) else { break; }; let as_name = as_name.token.content.replace('\"', ""); let Some(ast::Expr::Literal(version)) = rec .get("version") .and_then(|version| version.body.block.first()) else { break; }; let version = version.token.content.replace('\"', ""); let path = rec.get("path").and_then(|path| { if let Some(ast::Expr::Literal(lit)) = path.body.block.first() { Some(lit.token.content.replace('\"', "")) } else { None } }); let package = erg_common::config::Package::new( name.leak(), as_name.leak(), version.leak(), path.map(|p| &*p.leak()), ); packages.push(package); } cfg.packages = Arc::from(packages); cfg } pub fn run(mut self) { let mut _self = self.clone(); let mut handle = spawn_new_thread( move || loop { let msg = match _self.read_message() { Ok(msg) => msg, Err(err) => { lsp_log!("error: {err}"); if err.kind() == io::ErrorKind::UnexpectedEof { panic!("unexpected EOF"); } continue; } }; if let Err(err) = _self.dispatch(msg) { lsp_log!("error: {err}"); if err.to_string().contains("sending on a closed channel") { _self .send_error_info("An error occurred. Restarting...") .unwrap(); _self.restart(); }; } }, fn_name!(), ); loop { // recover from crash if handle.is_finished() { let _ = self.send_error_info("The compiler has crashed. Restarting..."); self.restart(); let mut _self = self.clone(); handle = spawn_new_thread( move || loop { let msg = match _self.read_message() { Ok(msg) => msg, Err(err) => { lsp_log!("error: {err}"); if err.kind() == io::ErrorKind::UnexpectedEof { panic!("unexpected EOF"); } continue; } }; if let Err(err) = _self.dispatch(msg) { lsp_log!("error: {err}"); if err.to_string().contains("sending on a closed channel") { _self .send_error_info("An error occurred again. Restarting...") .unwrap(); _self.restart(); }; } }, fn_name!(), ); } safe_yield(); } // Ok(()) } pub const fn mode(&self) -> &str { if PYTHON_MODE { "pylyzer" } else { "erg" } } fn read_line(&self) -> io::Result { let mut line = String::new(); stdin().lock().read_line(&mut line)?; Ok(line) } fn read_exact(&self, len: usize) -> io::Result> { let mut buf = vec![0; len]; stdin().lock().read_exact(&mut buf)?; Ok(buf) } #[allow(clippy::field_reassign_with_default)] fn init(&mut self, msg: &Value, id: i64) -> ELSResult<()> { self.send_log("initializing ELS")?; if msg.get("params").is_some() && msg["params"].get("capabilities").is_some() { self.init_params = InitializeParams::deserialize(&msg["params"])?; // self.send_log(format!("set client capabilities: {:?}", self.client_capas))?; } let mut result = InitializeResult::default(); result.capabilities = self.init_capabilities(); self.init_services(); self.send_stdout(&json!({ "jsonrpc": "2.0", "id": id, "result": result, })) } #[allow(clippy::field_reassign_with_default)] fn init_capabilities(&mut self) -> ServerCapabilities { let mut capabilities = ServerCapabilities::default(); self.file_cache.set_capabilities(&mut capabilities); let mut comp_options = CompletionOptions::default(); comp_options.trigger_characters = Some(TRIGGER_CHARS.map(String::from).to_vec()); comp_options.resolve_provider = Some(true); capabilities.completion_provider = if self .disabled_features .contains(&DefaultFeatures::Completion) { None } else { Some(comp_options) }; capabilities.rename_provider = Some(OneOf::Left(true)); capabilities.references_provider = Some(OneOf::Left(true)); capabilities.definition_provider = Some(OneOf::Left(true)); capabilities.type_definition_provider = Some(TypeDefinitionProviderCapability::Simple(true)); capabilities.implementation_provider = Some(ImplementationProviderCapability::Simple(true)); capabilities.hover_provider = self .disabled_features .contains(&DefaultFeatures::Hover) .not() .then_some(HoverProviderCapability::Simple(true)); capabilities.inlay_hint_provider = self .disabled_features .contains(&DefaultFeatures::InlayHint) .not() .then_some(OneOf::Right(InlayHintServerCapabilities::Options( InlayHintOptions { resolve_provider: Some(true), ..Default::default() }, ))); let mut sema_options = SemanticTokensOptions::default(); sema_options.range = Some(false); sema_options.full = Some(SemanticTokensFullOptions::Bool(true)); sema_options.legend = SemanticTokensLegend { token_types: vec![ SemanticTokenType::NAMESPACE, SemanticTokenType::TYPE, SemanticTokenType::CLASS, SemanticTokenType::INTERFACE, SemanticTokenType::TYPE_PARAMETER, SemanticTokenType::PARAMETER, SemanticTokenType::VARIABLE, SemanticTokenType::PROPERTY, SemanticTokenType::FUNCTION, SemanticTokenType::METHOD, SemanticTokenType::STRING, SemanticTokenType::NUMBER, SemanticTokenType::OPERATOR, ], token_modifiers: vec![ SemanticTokenModifier::DECLARATION, SemanticTokenModifier::DEFINITION, SemanticTokenModifier::READONLY, SemanticTokenModifier::STATIC, SemanticTokenModifier::DEPRECATED, SemanticTokenModifier::ABSTRACT, SemanticTokenModifier::ASYNC, SemanticTokenModifier::MODIFICATION, SemanticTokenModifier::DOCUMENTATION, SemanticTokenModifier::DEFAULT_LIBRARY, ], }; capabilities.semantic_tokens_provider = self .disabled_features .contains(&DefaultFeatures::SemanticTokens) .not() .then_some(SemanticTokensServerCapabilities::SemanticTokensOptions( sema_options, )); capabilities.code_action_provider = if self .disabled_features .contains(&DefaultFeatures::CodeAction) { None } else { let options = CodeActionProviderCapability::Options(CodeActionOptions { code_action_kinds: Some(vec![CodeActionKind::QUICKFIX, CodeActionKind::REFACTOR]), resolve_provider: Some(true), work_done_progress_options: WorkDoneProgressOptions::default(), }); Some(options) }; capabilities.execute_command_provider = Some(ExecuteCommandOptions { commands: vec![format!("{}.eliminate_unused_vars", self.mode())], work_done_progress_options: WorkDoneProgressOptions::default(), }); capabilities.signature_help_provider = self .disabled_features .contains(&DefaultFeatures::SignatureHelp) .not() .then_some(SignatureHelpOptions { trigger_characters: Some(vec!["(".to_string(), ",".to_string(), "|".to_string()]), retrigger_characters: None, work_done_progress_options: WorkDoneProgressOptions { work_done_progress: None, }, }); capabilities.code_lens_provider = if self.disabled_features.contains(&DefaultFeatures::CodeLens) { None } else { Some(CodeLensOptions { resolve_provider: Some(false), }) }; capabilities.workspace_symbol_provider = Some(OneOf::Left(true)); capabilities.document_symbol_provider = Some(OneOf::Left(true)); capabilities.document_link_provider = if self .disabled_features .contains(&DefaultFeatures::DocumentLink) { None } else { Some(DocumentLinkOptions { resolve_provider: Some(false), work_done_progress_options: Default::default(), }) }; capabilities.call_hierarchy_provider = Some(CallHierarchyServerCapability::Simple(true)); capabilities.folding_range_provider = Some(FoldingRangeProviderCapability::Simple( self.disabled_features .contains(&DefaultFeatures::FoldingRange) .not(), )); capabilities.selection_range_provider = Some(SelectionRangeProviderCapability::Simple(true)); capabilities.document_highlight_provider = Some(OneOf::Left( self.disabled_features .contains(&DefaultFeatures::DocumentHighlight) .not(), )); capabilities } pub(crate) fn ask_auto_save(&self) -> ELSResult<()> { let params = ConfigurationParams { items: vec![ConfigurationItem { scope_uri: None, section: Some("files.autoSave".to_string()), }], }; self.send_stdout(&json!({ "jsonrpc": "2.0", "id": ASK_AUTO_SAVE_ID, "method": "workspace/configuration", "params": params, })) } fn start_language_services(&mut self) { let (senders, receivers) = SendChannels::new(); self.channels = Some(senders); self.start_service::(receivers.completion, Self::handle_completion); self.start_service::( receivers.resolve_completion, Self::handle_resolve_completion, ); self.start_service::( receivers.goto_definition, Self::handle_goto_definition, ); self.start_service::( receivers.goto_type_definition, Self::handle_goto_type_definition, ); self.start_service::( receivers.goto_implementation, Self::handle_goto_implementation, ); self.start_service::( receivers.semantic_tokens_full, Self::handle_semantic_tokens_full, ); self.start_service::(receivers.inlay_hint, Self::handle_inlay_hint); self.start_service::( receivers.inlay_hint_resolve, Self::handle_inlay_hint_resolve, ); self.start_service::(receivers.hover, Self::handle_hover); self.start_service::(receivers.references, Self::handle_references); self.start_service::(receivers.code_lens, Self::handle_code_lens); self.start_service::(receivers.code_action, Self::handle_code_action); self.start_service::( receivers.code_action_resolve, Self::handle_code_action_resolve, ); self.start_service::( receivers.signature_help, Self::handle_signature_help, ); self.start_service::( receivers.will_rename_files, Self::handle_will_rename_files, ); self.start_service::( receivers.execute_command, Self::handle_execute_command, ); self.start_service::( receivers.workspace_symbol, Self::handle_workspace_symbol, ); self.start_service::( receivers.document_symbol, Self::handle_document_symbol, ); self.start_service::( receivers.call_hierarchy_prepare, Self::handle_call_hierarchy_prepare, ); self.start_service::( receivers.call_hierarchy_incoming, Self::handle_call_hierarchy_incoming, ); self.start_service::( receivers.call_hierarchy_outgoing, Self::handle_call_hierarchy_outgoing, ); self.start_service::( receivers.folding_range, Self::handle_folding_range, ); self.start_service::( receivers.selection_range, Self::handle_selection_range, ); self.start_service::( receivers.document_highlight, Self::handle_document_highlight, ); self.start_service::( receivers.document_link, Self::handle_document_link, ); self.start_client_health_checker(receivers.health_check); } fn init_services(&mut self) { self.start_language_services(); self.start_workspace_diagnostics(); self.start_auto_diagnostics(); } fn exit(&self) -> ELSResult<()> { self.send_log("exiting ELS")?; std::process::exit(0); } fn shutdown(&self, id: i64) -> ELSResult<()> { self.send_log("shutting down ELS")?; self.send_stdout(&json!({ "jsonrpc": "2.0", "id": id, "result": json!(null), })) } /// Restart the server. Clear caches and close & reopen channels. /// The server does not erase client-dependent information because it cannot notify the client of the restart and receive resources (such as workspace files) again. #[allow(unused)] pub(crate) fn restart(&mut self) { lsp_log!("restarting ELS"); // self.file_cache.clear(); self.comp_cache.clear(); if let Some(chan) = self.channels.as_ref() { chan.close(); } self.shared.clear_all(); self.start_language_services(); self.start_workspace_diagnostics(); } /// Copied and modified from RLS, https://github.com/rust-lang/rls/blob/master/rls/src/server/io.rs fn read_message(&self) -> Result { // Read in the "Content-Length: xx" part. let mut size: Option = None; loop { let buffer = self.read_line()?; // End of input. if buffer.is_empty() { return Err(io::Error::new( io::ErrorKind::UnexpectedEof, "EOF encountered in the middle of reading LSP headers", )); } // Header section is finished, break from the loop. if buffer == "\r\n" { break; } let res: Vec<&str> = buffer.split(' ').collect(); // Make sure header is valid. if res.len() != 2 { return Err(io::Error::new( io::ErrorKind::InvalidData, format!("Header '{buffer}' is malformed"), )); } let header_name = res[0].to_lowercase(); let header_value = res[1].trim(); match header_name.as_ref() { "content-length:" => { size = Some(header_value.parse::().map_err(|_e| { io::Error::new(io::ErrorKind::InvalidData, "Couldn't read size") })?); } "content-type:" => { if header_value != "utf8" && header_value != "utf-8" { return Err(io::Error::new( io::ErrorKind::InvalidData, format!("Content type '{header_value}' is invalid"), )); } } // Ignore unknown headers (specification doesn't say what to do in this case). _ => (), } } let size = match size { Some(size) => size, None => { return Err(io::Error::new( io::ErrorKind::InvalidData, "Message is missing 'content-length' header", )); } }; let content = self.read_exact(size)?; let s = String::from_utf8(content) .map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))?; Ok(Value::from_str(&s)?) } pub fn dispatch(&mut self, msg: Value) -> ELSResult<()> { match ( msg.get("id") .and_then(|i| i.as_i64().or(i.as_str().and_then(|s| s.parse().ok()))), msg.get("method").and_then(|m| m.as_str()), ) { (Some(id), Some(method)) => self.handle_request(&msg, id, method), (Some(id), None) => self.handle_response(id, &msg), (None, Some(notification)) => self.handle_notification(&msg, notification), _ => self.send_invalid_req_error(), } } fn parse_send(&self, id: i64, msg: &Value) -> ELSResult<()> where R: lsp_types::request::Request + 'static, R::Result: Serialize, Server: Sendable, { let params = R::Params::deserialize(&msg["params"])?; self.send(id, params)?; Ok(()) } fn start_service( &self, receiver: mpsc::Receiver>, handler: Handler, R::Params, R::Result>, ) where R: lsp_types::request::Request + 'static, R::Params: Send, R::Result: Serialize, { let mut _self = self.clone(); spawn_new_thread( move || loop { match receiver.recv() { Ok(WorkerMessage::Request(id, params)) => { _self.scheduler.register(id, R::METHOD); if _self.scheduler.acquire(id).is_none() { _log!(_self, "canceled: {id}"); continue; } match handler(&mut _self, params) { Ok(result) => { let _ = _self.send_stdout(&LSPResult::new(id, result)); } Err(err) => { lsp_log!("error: {err}"); let _ = _self.send_stdout(&ErrorMessage::new( Some(id), format!("err from {}: {err}", type_name::()).into(), )); } } _self.scheduler.finish(id); } Ok(WorkerMessage::Kill) => { break; } Err(err) => { lsp_log!("error: {err}"); _log!(_self, "error: {err}"); break; } } }, // The receiver channel will be dropped and closed R::METHOD, ); } fn start_work_done_progress(&self, title: &str) -> NumberOrString { let token = NumberOrString::String(title.to_string()); let progress_token = WorkDoneProgressCreateParams { token: token.clone(), }; let _ = self.send_stdout(&json!({ "jsonrpc": "2.0", "id": 1, "method": "window/workDoneProgress/create", "params": progress_token, })); let progress_begin = WorkDoneProgressBegin { title: title.to_string(), cancellable: Some(false), message: None, percentage: Some(0), }; let params = ProgressParams { token: token.clone(), value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(progress_begin)), }; let _ = self.send_stdout(&json!({ "jsonrpc": "2.0", "method": "$/progress", "params": params, })); token } fn stop_work_done_progress(&self, token: NumberOrString, message: &str) { let progress_end = WorkDoneProgressEnd { message: Some(message.to_string()), }; let params = ProgressParams { token, value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(progress_end)), }; let _ = self.send_stdout(&json!({ "jsonrpc": "2.0", "method": "$/progress", "params": params, })); } fn handle_request(&mut self, msg: &Value, id: i64, method: &str) -> ELSResult<()> { match method { "initialize" => self.init(msg, id), "shutdown" => self.shutdown(id), Rename::METHOD => self.rename(msg), Completion::METHOD => self.parse_send::(id, msg), ResolveCompletionItem::METHOD => self.parse_send::(id, msg), GotoDefinition::METHOD => self.parse_send::(id, msg), GotoTypeDefinition::METHOD => self.parse_send::(id, msg), GotoImplementation::METHOD => self.parse_send::(id, msg), HoverRequest::METHOD => self.parse_send::(id, msg), References::METHOD => self.parse_send::(id, msg), SemanticTokensFullRequest::METHOD => { self.parse_send::(id, msg) } InlayHintRequest::METHOD => self.parse_send::(id, msg), InlayHintResolveRequest::METHOD => self.parse_send::(id, msg), CodeActionRequest::METHOD => self.parse_send::(id, msg), CodeActionResolveRequest::METHOD => { self.parse_send::(id, msg) } SignatureHelpRequest::METHOD => self.parse_send::(id, msg), CodeLensRequest::METHOD => self.parse_send::(id, msg), WillRenameFiles::METHOD => self.parse_send::(id, msg), ExecuteCommand::METHOD => self.parse_send::(id, msg), WorkspaceSymbol::METHOD => self.parse_send::(id, msg), DocumentSymbolRequest::METHOD => self.parse_send::(id, msg), CallHierarchyIncomingCalls::METHOD => { self.parse_send::(id, msg) } CallHierarchyOutgoingCalls::METHOD => { self.parse_send::(id, msg) } CallHierarchyPrepare::METHOD => self.parse_send::(id, msg), FoldingRangeRequest::METHOD => self.parse_send::(id, msg), SelectionRangeRequest::METHOD => self.parse_send::(id, msg), DocumentHighlightRequest::METHOD => { self.parse_send::(id, msg) } DocumentLinkRequest::METHOD => self.parse_send::(id, msg), other => self.send_error(Some(id), -32600, format!("{other} is not supported")), } } fn handle_notification(&mut self, msg: &Value, method: &str) -> ELSResult<()> { match method { "initialized" => { self.flags.client_initialized.store(true, Ordering::Relaxed); self.ask_auto_save()?; self.send_log("successfully bound") } "exit" => self.exit(), "textDocument/didOpen" => { while !self.flags.workspace_checked() { safe_yield(); } let params = DidOpenTextDocumentParams::deserialize(msg["params"].clone())?; let uri = NormalizedUrl::new(params.text_document.uri); self.send_log(format!("{method}: {uri}"))?; let code = params.text_document.text; let ver = params.text_document.version; self.file_cache.update(&uri, code.clone(), Some(ver)); let token = self.start_work_done_progress("checking files ..."); let mut checked = Set::new(); let res = self.check_file(uri.clone(), code, &mut checked); // self.send_empty_diagnostics(checked)?; self.stop_work_done_progress(token, "checking done"); res } "textDocument/didSave" => { let params = DidSaveTextDocumentParams::deserialize(msg["params"].clone())?; let uri = NormalizedUrl::new(params.text_document.uri); self.send_log(format!("{method}: {uri}"))?; let code = self.file_cache.get_entire_code(&uri)?; let token = self.start_work_done_progress("checking files ..."); let res = self.recheck_file(uri, code); self.stop_work_done_progress(token, "checking done"); res } "textDocument/didChange" => { let params = DidChangeTextDocumentParams::deserialize(msg["params"].clone())?; // Check before updating, because `x.`/`x::` will result in an error // Checking should only be performed when needed for completion, i.e., when a trigger character is entered or at the beginning of a line if TRIGGER_CHARS.contains(&¶ms.content_changes[0].text[..]) || params.content_changes[0] .range .is_some_and(|r| r.start.character == 0) { let uri = NormalizedUrl::new(params.text_document.uri.clone()); // TODO: reset mutable dependent types self.quick_check_file(uri)?; } self.file_cache.incremental_update(params); Ok(()) } "$/cancelRequest" => { let id = msg["params"]["id"].as_i64().unwrap(); let task = self.scheduler.cancel(id); if task.is_some() { _log!(self, "cancellation approved: {id}"); } Ok(()) } _ => self.send_log(format!("received notification: {method}")), } } fn handle_response(&mut self, id: i64, msg: &Value) -> ELSResult<()> { match id { HEALTH_CHECKER_ID => { self.channels .as_ref() .ok_or("channels not found")? .health_check .send(WorkerMessage::Request(0, ()))?; } _ => { _log!(self, "msg: {msg}"); if msg.get("error").is_none() { self.client_answers.borrow_mut().insert(id, msg.clone()); } } } Ok(()) } pub(crate) fn get_mod_ctx( &self, uri: &NormalizedUrl, ) -> Option> { let path = uri.to_file_path().ok()?; let ent = self.shared.get_module(&path)?; Some(MappedRwLockReadGuard::map(ent, |ent| &ent.module)) } pub(crate) fn get_raw_mod_ctx( &self, uri: &NormalizedUrl, timeout: std::time::Duration, ) -> Option<&ModuleContext> { let path = uri.to_file_path().ok()?; self.shared.raw_ref_ctx_with_timeout(&path, timeout) } /// TODO: Reuse cache. /// Because of the difficulty of caching "transitional types" such as assert casting and mutable dependent types, /// the cache is deleted after each analysis. pub(crate) fn get_checker(&self, path: PathBuf) -> Checker { let shared = self.shared.clone(); shared.clear(&NormalizedPathBuf::from(&path)); Checker::inherit(self.cfg.inherit(path), shared) } pub(crate) fn steal_lowerer(&mut self, uri: &NormalizedUrl) -> Option<(ASTLowerer, IRs)> { let path = uri.to_file_path().ok()?; let module = self.shared.remove_module(&path)?; let lowerer = ASTLowerer::new_with_ctx(module.module); Some(( lowerer, IRs::new(module.id, module.ast, module.hir, module.status), )) } pub(crate) fn restore_lowerer( &mut self, uri: NormalizedUrl, mut lowerer: ASTLowerer, irs: IRs, ) { let module = lowerer.pop_mod_ctx().unwrap(); let entry = ModuleEntry::new(irs.id, irs.ast, irs.hir, module, irs.status); self.restore_entry(uri, entry); } pub(crate) fn get_visitor(&self, uri: &NormalizedUrl) -> Option { let path = uri.to_file_path().ok()?; let Some(ent) = self.shared.get_module(&path) else { _log!(self, "module not found: {uri}"); return None; }; ent.hir.as_ref()?; let hir = MappedRwLockReadGuard::map(ent, |ent| ent.hir.as_ref().unwrap()); Some(HIRVisitor::new(hir, &self.file_cache, uri.clone())) } pub(crate) fn get_searcher(&self, uri: &NormalizedUrl, kind: ExprKind) -> Option { let path = uri.to_file_path().ok()?; let ent = self.shared.get_module(&path)?; ent.hir.as_ref()?; let hir = MappedRwLockReadGuard::map(ent, |ent| ent.hir.as_ref().unwrap()); Some(HIRVisitor::new_searcher( hir, &self.file_cache, uri.clone(), kind, )) } pub(crate) fn get_local_ctx(&self, uri: &NormalizedUrl, pos: Position) -> Vec<&Context> { let mut ctxs = vec![]; if let Some(mod_ctx) = self.get_raw_mod_ctx(uri, Duration::from_millis(100)) { if let Some(visitor) = self.get_visitor(uri) { // FIXME: let mut ns = visitor.get_namespace(pos); if &mod_ctx.context.name[..] == "" { ns[0] = "".into(); } for i in 1..ns.len() { let ns = ns[..=ns.len() - i].join(""); if let Some(ctx) = mod_ctx.scope.get(&ns[..]) { ctxs.push(ctx); } } } ctxs.push(&mod_ctx.context); } let builtin_ctx = self.get_raw_builtin_module(); ctxs.extend(builtin_ctx); ctxs } pub(crate) fn _get_all_ctxs(&self) -> Vec<&ModuleContext> { let mut ctxs = vec![]; ctxs.extend(self.shared.raw_modules().map(|ent| &ent.module)); ctxs } pub(crate) fn get_workspace_ctxs(&self) -> Vec<&Context> { let project_root = project_entry_dir_of(&self.home).unwrap_or(self.home.clone()); let mut ctxs = vec![]; for (path, ent) in self.shared.raw_path_and_modules() { if path.starts_with(&project_root) { ctxs.push(&ent.module.context); } } ctxs } /// the ctx of `uri` is not included pub(crate) fn get_neighbor_ctxs(&self, uri: &NormalizedUrl) -> Vec<&Context> { let mut ctxs = vec![]; if let Ok(dir) = uri .to_file_path() .and_then(|p| p.parent().ok_or(())?.read_dir().map_err(|_| ())) { for neighbor in dir { let Ok(neighbor) = neighbor else { continue; }; let neighbor_uri = NormalizedUrl::from_file_path(neighbor.path()).unwrap(); if &neighbor_uri == uri { continue; } if let Some(mod_ctx) = self.get_raw_mod_ctx(&neighbor_uri, Duration::from_millis(50)) { ctxs.push(&mod_ctx.context); } } } ctxs } pub(crate) fn get_receiver( &self, uri: &NormalizedUrl, attr_marker_pos: Position, ) -> ELSResult> { let maybe_token = self.file_cache.get_receiver(uri, attr_marker_pos); if let Some(token) = maybe_token { let expr = if let Some(visitor) = self.get_visitor(uri) { if let Some(expr) = loc_to_pos(token.loc()).and_then(|pos| visitor.get_min_expr(pos).cloned()) { Some(expr) } else { _log!(self, "expr not found: {token}"); None } } else { None }; Ok(expr) } else { self.send_log("token not found")?; Ok(None) } } pub(crate) fn get_receiver_and_ctxs( &self, uri: &NormalizedUrl, attr_marker_pos: Position, ) -> ELSResult<(Option, Vec<&Context>)> { let Some(module) = self.get_raw_mod_ctx(uri, Duration::from_millis(100)) else { return Ok((None, vec![])); }; let maybe_token = self.file_cache.get_receiver(uri, attr_marker_pos); if let Some(token) = maybe_token { // _log!(self, "token: {token}"); let mut ctxs = vec![]; let expr = if let Some(visitor) = self.get_visitor(uri) { if let Some(expr) = loc_to_pos(token.loc()).and_then(|pos| visitor.get_min_expr(pos)) { let type_ctxs = module .context .get_nominal_super_type_ctxs(expr.ref_t()) .unwrap_or(vec![]); ctxs.extend(type_ctxs.into_iter().map(|ctx| &ctx.ctx)); if let Ok(singular_ctxs) = module .context .get_singular_ctxs_by_hir_expr(expr, &module.context) { ctxs.extend(singular_ctxs); } Some(expr.t()) } else { _log!(self, "expr not found: {token}"); None } } else { None }; Ok((expr, ctxs)) } else { self.send_log("token not found")?; Ok((None, vec![])) } } pub(crate) fn get_raw_builtin_module(&self) -> Option<&Context> { self.shared.raw_ref_builtins_ctx().map(|mc| &mc.context) } pub(crate) fn clear_cache(&mut self, uri: &NormalizedUrl) { let path = NormalizedPathBuf::from(util::uri_to_path(uri)); self.shared.clear(&path); } #[allow(unused)] pub fn get_file_cache(&self) -> &FileCache { &self.file_cache } pub fn remove_module_entry(&mut self, uri: &NormalizedUrl) -> Option { let path = uri.to_file_path().ok()?; self.shared.remove_module(&path) } pub fn insert_module_entry(&mut self, uri: NormalizedUrl, entry: ModuleEntry) { let Ok(path) = uri.to_file_path() else { return; }; self.shared.insert_module(path.into(), entry); } pub fn get_hir(&self, uri: &NormalizedUrl) -> Option> { let path = uri.to_file_path().ok()?; let ent = self.shared.get_module(&path)?; MappedRwLockReadGuard::try_map(ent, |ent| ent.hir.as_ref()).ok() } pub fn steal_entry(&self, uri: &NormalizedUrl) -> Option { let path = uri.to_file_path().ok()?; self.shared.remove_module(&path) } pub fn restore_entry(&self, uri: NormalizedUrl, entry: ModuleEntry) { let path = uri.to_file_path().unwrap(); self.shared.insert_module(path.into(), entry); } pub fn get_ast(&self, uri: &NormalizedUrl) -> Option> { let path = uri.to_file_path().ok()?; let ent = self.shared.get_module(&path)?; MappedRwLockReadGuard::try_map(ent, |ent| ent.ast.as_ref()).ok() } pub fn get_warns(&self, uri: &NormalizedUrl) -> Option> { let path = NormalizedPathBuf::from(uri.to_file_path().ok()?); let warns = self.shared.warns.raw_iter(); let warns = warns.filter(|warn| NormalizedPathBuf::from(warn.input.path()) == path); Some(warns.collect()) } }