#[cfg(not(target_family = "wasm"))]
pub mod phpcs;
#[cfg(not(target_family = "wasm"))]
pub mod phpstan;
#[cfg(not(target_family = "wasm"))]
use std::path::Path;
#[cfg(not(target_family = "wasm"))]
use tower_lsp_server::ls_types::Diagnostic;
#[cfg(not(target_family = "wasm"))]
use crate::lang::config::ExternalToolsConfig;
#[cfg(not(target_family = "wasm"))]
const MAX_CONCURRENT_RUNS: usize = 2;
#[cfg(not(target_family = "wasm"))]
static RUN_SLOTS: tokio::sync::Semaphore = tokio::sync::Semaphore::const_new(MAX_CONCURRENT_RUNS);
#[cfg(not(target_family = "wasm"))]
pub async fn acquire_run_slot() -> tokio::sync::SemaphorePermit<'static> {
RUN_SLOTS
.acquire()
.await
.expect("RUN_SLOTS is never closed")
}
#[cfg(not(target_family = "wasm"))]
#[derive(Default)]
pub struct RunRegistry {
runs: dashmap::DashMap<tower_lsp_server::ls_types::Uri, tokio::task::AbortHandle>,
}
#[cfg(not(target_family = "wasm"))]
impl RunRegistry {
pub fn start(
&self,
uri: tower_lsp_server::ls_types::Uri,
run: impl std::future::Future<Output = ()> + Send + 'static,
) {
let handle = tokio::spawn(run).abort_handle();
if let Some(previous) = self.runs.insert(uri, handle) {
previous.abort();
}
}
pub fn cancel(&self, uri: &tower_lsp_server::ls_types::Uri) {
if let Some((_, run)) = self.runs.remove(uri) {
run.abort();
}
}
}
#[cfg(unix)]
struct ProcessGroupGuard(Option<u32>);
#[cfg(unix)]
impl Drop for ProcessGroupGuard {
fn drop(&mut self) {
if let Some(pgid) = self.0.and_then(|pid| i32::try_from(pid).ok()) {
unsafe {
libc::killpg(pgid, libc::SIGKILL);
}
}
}
}
#[cfg(not(target_family = "wasm"))]
async fn output_with_timeout(
mut command: tokio::process::Command,
timeout: std::time::Duration,
tool: &str,
) -> Option<std::process::Output> {
command.kill_on_drop(true);
#[cfg(unix)]
command.process_group(0);
let child = match command.spawn() {
Ok(child) => child,
Err(e) => {
tracing::debug!("{tool} did not run: {e}");
return None;
}
};
#[cfg(unix)]
let _group = ProcessGroupGuard(child.id());
match tokio::time::timeout(timeout, child.wait_with_output()).await {
Ok(Ok(output)) => Some(output),
Ok(Err(e)) => {
tracing::debug!("{tool} did not run: {e}");
None
}
Err(_) => {
tracing::warn!("{tool} killed after {timeout:?}");
None
}
}
}
#[cfg(not(target_family = "wasm"))]
pub async fn run_external_diagnostics(
cfg: &ExternalToolsConfig,
path: &Path,
workspace_root: Option<&Path>,
) -> Vec<Diagnostic> {
let timeout = std::time::Duration::from_millis(cfg.timeout_ms);
let mut out = Vec::new();
if cfg.phpstan.enabled {
out.extend(phpstan::run(&cfg.phpstan, path, workspace_root, timeout).await);
}
if cfg.phpcs.enabled {
out.extend(phpcs::run(&cfg.phpcs, path, workspace_root, timeout).await);
}
out
}