Skip to content

Commit

Permalink
Showing 5 changed files with 197 additions and 16 deletions.
109 changes: 101 additions & 8 deletions crates/els/diagnostics.rs
Original file line number Diff line number Diff line change
@@ -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<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
pub(crate) fn get_ast(&self, uri: &NormalizedUrl) -> Option<Module> {
@@ -267,6 +270,96 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
);
}

fn project_files(dir: PathBuf) -> Vec<NormalizedUrl> {
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(&current_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<WorkerMessage<()>>) {
65 changes: 63 additions & 2 deletions crates/els/server.rs
Original file line number Diff line number Diff line change
@@ -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<NormalizedUrl, ModuleContext> {
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<AtomicBool>,
pub(crate) workspace_checked: Arc<AtomicBool>,
}

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<Checker: BuildRunnable = HIRBuilder, Parser: Parsable = Simple
pub(crate) comp_cache: CompletionCache,
// TODO: remove modules, analysis_result, and add `shared: SharedCompilerResource`
pub(crate) modules: ModuleCache,
pub(crate) flags: Flags,
pub(crate) analysis_result: AnalysisResultCache,
pub(crate) channels: Option<SendChannels>,
pub(crate) stdout_redirect: Option<mpsc::Sender<Value>>,
@@ -319,6 +344,7 @@ impl<C: BuildRunnable, P: Parsable> Clone for Server<C, P> {
modules: self.modules.clone(),
analysis_result: self.analysis_result.clone(),
channels: self.channels.clone(),
flags: self.flags.clone(),
stdout_redirect: self.stdout_redirect.clone(),
_parser: std::marker::PhantomData,
_checker: std::marker::PhantomData,
@@ -341,6 +367,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
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<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {

fn init_services(&mut self) {
self.start_language_services();
self.start_workspace_diagnostics();
self.start_auto_diagnostics();
}

@@ -613,6 +641,7 @@ impl<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
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<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
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<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
ctxs
}

pub(crate) fn _get_all_ctxs(&self) -> Vec<Arc<ModuleContext>> {
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
9 changes: 3 additions & 6 deletions crates/els/symbol.rs
Original file line number Diff line number Diff line change
@@ -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<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
) -> ELSResult<Option<Vec<SymbolInformation>>> {
_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<Checker: BuildRunnable, Parser: Parsable> Server<Checker, Parser> {
{
continue;
}
if !params.query.is_empty() && !name.inspect().contains(&params.query) {
continue;
}
let Some(location) = abs_loc_to_lsp_loc(&vi.def_loc) else {
continue;
};
14 changes: 14 additions & 0 deletions crates/els/util.rs
Original file line number Diff line number Diff line change
@@ -183,3 +183,17 @@ pub(crate) fn abs_loc_to_lsp_loc(loc: &AbsLocation) -> Option<lsp_types::Locatio
let range = loc_to_range(loc.loc)?;
Some(lsp_types::Location::new(uri, range))
}

pub(crate) fn project_root_of(path: &Path) -> Option<PathBuf> {
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
}
16 changes: 16 additions & 0 deletions crates/erg_compiler/module/cache.rs
Original file line number Diff line number Diff line change
@@ -153,6 +153,10 @@ impl ModuleCache {
self.cache.iter()
}

pub fn values(&self) -> impl Iterator<Item = &ModuleEntry> {
self.cache.values()
}

pub fn clear(&mut self) {
self.cache.clear();
}
@@ -284,4 +288,16 @@ impl SharedModuleCache {
pub fn ref_inner(&self) -> MappedRwLockReadGuard<Dict<NormalizedPathBuf, ModuleEntry>> {
RwLockReadGuard::map(self.0.borrow(), |mc| &mc.cache)
}

pub fn raw_values(&self) -> impl Iterator<Item = &ModuleEntry> {
let _ref = self.0.borrow();
let ref_ = unsafe { self.0.as_ptr().as_ref().unwrap() };
ref_.values()
}

pub fn raw_iter(&self) -> impl Iterator<Item = (&NormalizedPathBuf, &ModuleEntry)> {
let _ref = self.0.borrow();
let ref_ = unsafe { self.0.as_ptr().as_ref().unwrap() };
ref_.iter()
}
}

0 comments on commit 64874f4

Please sign in to comment.