From 64874f41697730a9091eb15c95412f7cdf8d6d12 Mon Sep 17 00:00:00 2001 From: Shunsuke Shibayama Date: Fri, 8 Sep 2023 19:32:08 +0900 Subject: [PATCH] feat(els): workspace diagnostics --- crates/els/diagnostics.rs | 109 ++++++++++++++++++++++++++-- crates/els/server.rs | 65 ++++++++++++++++- crates/els/symbol.rs | 9 +-- crates/els/util.rs | 14 ++++ crates/erg_compiler/module/cache.rs | 16 ++++ 5 files changed, 197 insertions(+), 16 deletions(-) diff --git a/crates/els/diagnostics.rs b/crates/els/diagnostics.rs index eefc934f1..2dc33a3a8 100644 --- a/crates/els/diagnostics.rs +++ b/crates/els/diagnostics.rs @@ -1,10 +1,14 @@ +use std::env::current_dir; +use std::ffi::OsStr; +use std::path::PathBuf; +use std::sync::atomic::Ordering; use std::sync::mpsc::Receiver; use std::thread::sleep; use std::time::Duration; use erg_common::consts::PYTHON_MODE; use erg_common::dict::Dict; -use erg_common::spawn::spawn_new_thread; +use erg_common::spawn::{safe_yield, spawn_new_thread}; use erg_common::style::*; use erg_common::traits::Stream; use erg_common::{fn_name, lsp_log}; @@ -14,19 +18,18 @@ use erg_compiler::erg_parser::parse::Parsable; use erg_compiler::error::CompileErrors; use lsp_types::{ - ConfigurationParams, Diagnostic, DiagnosticSeverity, NumberOrString, Position, - PublishDiagnosticsParams, Range, Url, + ConfigurationParams, Diagnostic, DiagnosticSeverity, NumberOrString, Position, ProgressParams, + ProgressParamsValue, PublishDiagnosticsParams, Range, Url, WorkDoneProgress, + WorkDoneProgressBegin, WorkDoneProgressCreateParams, WorkDoneProgressEnd, }; use serde_json::json; use crate::_log; use crate::channels::WorkerMessage; use crate::diff::{ASTDiff, HIRDiff}; -use crate::server::{ - AnalysisResult, DefaultFeatures, ELSResult, RedirectableStdout, Server, ASK_AUTO_SAVE_ID, - HEALTH_CHECKER_ID, -}; -use crate::util::{self, NormalizedUrl}; +use crate::server::{AnalysisResult, DefaultFeatures, ELSResult, RedirectableStdout, Server}; +use crate::server::{ASK_AUTO_SAVE_ID, HEALTH_CHECKER_ID}; +use crate::util::{self, project_root_of, NormalizedUrl}; impl Server { pub(crate) fn get_ast(&self, uri: &NormalizedUrl) -> Option { @@ -267,6 +270,96 @@ impl Server { ); } + fn project_files(dir: PathBuf) -> Vec { + let mut uris = vec![]; + let Ok(read_dir) = dir.read_dir() else { + return uris; + }; + for entry in read_dir { + let Ok(entry) = entry else { + continue; + }; + if entry.path().extension() == Some(OsStr::new("er")) { + if let Ok(uri) = NormalizedUrl::from_file_path(entry.path()) { + uris.push(uri); + } + } else if entry.path().is_dir() { + uris.extend(Self::project_files(entry.path())); + } + } + uris + } + + pub(crate) fn start_workspace_diagnostics(&mut self) { + let mut _self = self.clone(); + spawn_new_thread( + move || { + while !_self.flags.client_initialized() { + safe_yield(); + } + let token = NumberOrString::String("els/start_workspace_diagnostics".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 Some(project_root) = project_root_of(¤t_dir().unwrap()) else { + _self.flags.workspace_checked.store(true, Ordering::Relaxed); + return; + }; + let src_dir = if project_root.join("src").is_dir() { + project_root.join("src") + } else { + project_root + }; + let Ok(main_uri) = NormalizedUrl::from_file_path(src_dir.join("main.er")) else { + _self.flags.workspace_checked.store(true, Ordering::Relaxed); + return; + }; + let uris = Self::project_files(src_dir); + let progress_begin = WorkDoneProgressBegin { + title: "Checking workspace".to_string(), + cancellable: Some(false), + message: Some(format!("checking {} files ...", uris.len())), + percentage: Some(0), + }; + let params = ProgressParams { + token: token.clone(), + value: ProgressParamsValue::WorkDone(WorkDoneProgress::Begin(progress_begin)), + }; + _self + .send_stdout(&json!({ + "jsonrpc": "2.0", + "method": "$/progress", + "params": params, + })) + .unwrap(); + let code = _self.file_cache.get_entire_code(&main_uri).unwrap(); + let _ = _self.check_file(main_uri, code); + let progress_end = WorkDoneProgressEnd { + message: Some(format!("checked {} files", uris.len())), + }; + let params = ProgressParams { + token: token.clone(), + value: ProgressParamsValue::WorkDone(WorkDoneProgress::End(progress_end)), + }; + _self + .send_stdout(&json!({ + "jsonrpc": "2.0", + "method": "$/progress", + "params": params, + })) + .unwrap(); + _self.flags.workspace_checked.store(true, Ordering::Relaxed); + }, + fn_name!(), + ); + } + /// Send an empty `workspace/configuration` request periodically. /// If there is no response to the request within a certain period of time, terminate the server. pub fn start_client_health_checker(&self, receiver: Receiver>) { diff --git a/crates/els/server.rs b/crates/els/server.rs index 24d14eccb..dcac866f8 100644 --- a/crates/els/server.rs +++ b/crates/els/server.rs @@ -4,7 +4,8 @@ use std::io::{stdin, BufRead, Read}; use std::ops::Not; use std::path::{Path, PathBuf}; use std::str::FromStr; -use std::sync::mpsc; +use std::sync::atomic::{AtomicBool, Ordering}; +use std::sync::{mpsc, Arc}; use erg_common::config::ErgConfig; use erg_common::consts::PYTHON_MODE; @@ -57,7 +58,7 @@ use crate::completion::CompletionCache; use crate::file_cache::FileCache; use crate::hir_visitor::{ExprKind, HIRVisitor}; use crate::message::{ErrorMessage, LSPResult}; -use crate::util::{self, loc_to_pos, NormalizedUrl}; +use crate::util::{self, loc_to_pos, project_root_of, NormalizedUrl}; pub const HEALTH_CHECKER_ID: i64 = 10000; pub const ASK_AUTO_SAVE_ID: i64 = 10001; @@ -261,6 +262,29 @@ impl ModuleCache { let ref_ = unsafe { self.0.as_ptr().as_ref() }; ref_.unwrap().values() } + + pub fn _iter(&self) -> std::collections::hash_map::Iter { + let _ref = self.0.borrow(); + let ref_ = unsafe { self.0.as_ptr().as_ref() }; + ref_.unwrap().iter() + } +} + +#[derive(Debug, Clone, Default)] +pub struct Flags { + pub(crate) client_initialized: Arc, + pub(crate) workspace_checked: Arc, +} + +impl Flags { + pub fn client_initialized(&self) -> bool { + self.client_initialized.load(Ordering::Relaxed) + } + + #[allow(unused)] + pub fn workspace_checked(&self) -> bool { + self.workspace_checked.load(Ordering::Relaxed) + } } /// A Language Server, which can be used any object implementing `BuildRunnable` internally by passing it as a generic parameter. @@ -277,6 +301,7 @@ pub struct Server, pub(crate) stdout_redirect: Option>, @@ -319,6 +344,7 @@ impl Clone for Server { modules: self.modules.clone(), analysis_result: self.analysis_result.clone(), channels: self.channels.clone(), + flags: self.flags.clone(), stdout_redirect: self.stdout_redirect.clone(), _parser: std::marker::PhantomData, _checker: std::marker::PhantomData, @@ -341,6 +367,7 @@ impl Server { modules: ModuleCache::new(), analysis_result: AnalysisResultCache::new(), channels: None, + flags: Flags::default(), stdout_redirect, _parser: std::marker::PhantomData, _checker: std::marker::PhantomData, @@ -588,6 +615,7 @@ impl Server { fn init_services(&mut self) { self.start_language_services(); + self.start_workspace_diagnostics(); self.start_auto_diagnostics(); } @@ -613,6 +641,7 @@ impl Server { self.analysis_result = AnalysisResultCache::new(); self.channels.as_ref().unwrap().close(); 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 @@ -780,6 +809,7 @@ impl Server { 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") } @@ -902,6 +932,37 @@ impl Server { ctxs } + pub(crate) fn _get_all_ctxs(&self) -> Vec> { + let mut ctxs = vec![]; + if let Some(shared) = self.get_shared() { + ctxs.extend(shared.mod_cache.raw_values().map(|ent| ent.module.clone())); + ctxs.extend( + shared + .py_mod_cache + .raw_values() + .map(|ent| ent.module.clone()), + ); + } + ctxs + } + + pub(crate) fn get_workspace_ctxs(&self) -> Vec<&Context> { + let project_root = project_root_of(&self.home).unwrap_or(self.home.clone()); + let mut ctxs = vec![]; + if let Some(shared) = self.get_shared() { + for (path, ent) in shared + .mod_cache + .raw_iter() + .chain(shared.py_mod_cache.raw_iter()) + { + if path.starts_with(&project_root) { + ctxs.push(&ent.module.context); + } + } + } + ctxs + } + pub(crate) fn get_neighbor_ctxs(&self, uri: &NormalizedUrl) -> Vec<&Context> { let mut ctxs = vec![]; if let Ok(dir) = uri diff --git a/crates/els/symbol.rs b/crates/els/symbol.rs index dba784a6f..f0574d424 100644 --- a/crates/els/symbol.rs +++ b/crates/els/symbol.rs @@ -1,8 +1,8 @@ use erg_common::traits::Locational; + use erg_compiler::artifact::BuildRunnable; use erg_compiler::erg_parser::ast::DefKind; use erg_compiler::erg_parser::parse::Parsable; - use erg_compiler::hir::Expr; use erg_compiler::ty::{HasType, Type}; use erg_compiler::varinfo::VarInfo; @@ -37,8 +37,8 @@ impl Server { ) -> ELSResult>> { _log!(self, "workspace symbol requested: {params:?}"); let mut res = vec![]; - for module in self.modules.values() { - for (name, vi) in module.context.local_dir() { + for context in self.get_workspace_ctxs() { + for (name, vi) in context.local_dir() { if name.inspect().starts_with(['%']) { continue; } @@ -49,9 +49,6 @@ impl Server { { continue; } - if !params.query.is_empty() && !name.inspect().contains(¶ms.query) { - continue; - } let Some(location) = abs_loc_to_lsp_loc(&vi.def_loc) else { continue; }; diff --git a/crates/els/util.rs b/crates/els/util.rs index 5b3035386..1ef2c57fc 100644 --- a/crates/els/util.rs +++ b/crates/els/util.rs @@ -183,3 +183,17 @@ pub(crate) fn abs_loc_to_lsp_loc(loc: &AbsLocation) -> Option Option { + if path.is_dir() && path.join("package.er").exists() { + return Some(path.to_path_buf()); + } + let mut path = path.to_path_buf(); + while let Some(parent) = path.parent() { + if parent.join("package.er").exists() { + return Some(parent.to_path_buf()); + } + path = parent.to_path_buf(); + } + None +} diff --git a/crates/erg_compiler/module/cache.rs b/crates/erg_compiler/module/cache.rs index 8e91816b9..846afa505 100644 --- a/crates/erg_compiler/module/cache.rs +++ b/crates/erg_compiler/module/cache.rs @@ -153,6 +153,10 @@ impl ModuleCache { self.cache.iter() } + pub fn values(&self) -> impl Iterator { + self.cache.values() + } + pub fn clear(&mut self) { self.cache.clear(); } @@ -284,4 +288,16 @@ impl SharedModuleCache { pub fn ref_inner(&self) -> MappedRwLockReadGuard> { RwLockReadGuard::map(self.0.borrow(), |mc| &mc.cache) } + + pub fn raw_values(&self) -> impl Iterator { + let _ref = self.0.borrow(); + let ref_ = unsafe { self.0.as_ptr().as_ref().unwrap() }; + ref_.values() + } + + pub fn raw_iter(&self) -> impl Iterator { + let _ref = self.0.borrow(); + let ref_ = unsafe { self.0.as_ptr().as_ref().unwrap() }; + ref_.iter() + } }