From 1625fbcd78c2eeafaca26936b7d26a90c0524e97 Mon Sep 17 00:00:00 2001 From: hanafish <1106510024@qq.com> Date: Tue, 11 Aug 2026 16:06:18 +0800 Subject: [PATCH] refactor(lsp): unify command detection --- src-tauri/crates/lsp/src/command_detection.rs | 53 +++++++++++++++++++ .../crates/lsp/src/commands/discovery.rs | 28 +--------- .../lsp/src/commands/package_manager.rs | 2 +- src-tauri/crates/lsp/src/install_pipeline.rs | 2 +- src-tauri/crates/lsp/src/lib.rs | 1 + src-tauri/crates/lsp/src/lint_tools.rs | 25 +-------- .../crates/lsp/src/workspace_scan/mod.rs | 3 +- .../lsp/src/workspace_scan/orchestrator.rs | 3 +- .../crates/lsp/src/workspace_scan/process.rs | 25 +-------- 9 files changed, 64 insertions(+), 78 deletions(-) create mode 100644 src-tauri/crates/lsp/src/command_detection.rs diff --git a/src-tauri/crates/lsp/src/command_detection.rs b/src-tauri/crates/lsp/src/command_detection.rs new file mode 100644 index 0000000000..616e02cdfb --- /dev/null +++ b/src-tauri/crates/lsp/src/command_detection.rs @@ -0,0 +1,53 @@ +use std::process::Command; + +/// Check whether a command-line tool is available on the system PATH. +/// +/// Forward PATH explicitly so app startup code that augments the process +/// environment is reflected consistently across every LSP discovery surface. +pub fn command_exists(command_name: &str) -> bool { + let current_path = std::env::var_os("PATH"); + + #[cfg(unix)] + let mut command = { + let mut command = Command::new("which"); + command.arg(command_name); + command + }; + + #[cfg(windows)] + let mut command = { + let mut command = Command::new("where"); + command.arg(command_name); + command + }; + + if let Some(path) = current_path { + command.env("PATH", path); + } + app_platform::hide_console(&mut command); + command + .output() + .map(|output| output.status.success()) + .unwrap_or(false) +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn finds_a_platform_shell() { + #[cfg(unix)] + assert!(command_exists("sh")); + + #[cfg(windows)] + assert!(command_exists("cmd")); + } + + #[test] + fn rejects_a_missing_command() { + assert!(!command_exists( + "orgii-command-detection-test-definitely-missing" + )); + } +} diff --git a/src-tauri/crates/lsp/src/commands/discovery.rs b/src-tauri/crates/lsp/src/commands/discovery.rs index 70ccf9353d..55f911c054 100644 --- a/src-tauri/crates/lsp/src/commands/discovery.rs +++ b/src-tauri/crates/lsp/src/commands/discovery.rs @@ -2,8 +2,6 @@ //! //! Tauri commands for detecting installed language servers and lint tools. -use std::process::Command; - use super::cache; use crate::lint_tools::LintToolInfo; use crate::server_defs::{servers, servers_for_language_id}; @@ -55,31 +53,7 @@ pub const LANGUAGE_DISPLAY_NAMES: &[(&str, &str)] = &[ ("zig", "Zig"), ]; -/// Check if a command exists in PATH -pub fn command_exists(cmd: &str) -> bool { - // Explicitly forward PATH so the login-shell-augmented PATH is visible. - let current_path = std::env::var("PATH").unwrap_or_default(); - #[cfg(unix)] - { - Command::new("which") - .arg(cmd) - .env("PATH", ¤t_path) - .output() - .map(|output| output.status.success()) - .unwrap_or(false) - } - #[cfg(windows)] - { - let mut command = Command::new("where"); - command.arg(cmd).env("PATH", ¤t_path); - // Suppress console window on Windows. - app_platform::hide_console(&mut command); - command - .output() - .map(|output| output.status.success()) - .unwrap_or(false) - } -} +pub use crate::command_detection::command_exists; /// Check if uninstall is supported based on install hint fn is_uninstall_supported(install_hint: &str) -> bool { diff --git a/src-tauri/crates/lsp/src/commands/package_manager.rs b/src-tauri/crates/lsp/src/commands/package_manager.rs index 7c9a32cc91..b7559b64c2 100644 --- a/src-tauri/crates/lsp/src/commands/package_manager.rs +++ b/src-tauri/crates/lsp/src/commands/package_manager.rs @@ -3,7 +3,7 @@ //! Utilities for detecting installed package managers and extracting //! package names from install hints. -use super::discovery::command_exists; +use crate::command_detection::command_exists; #[cfg(test)] #[path = "tests/package_manager_tests.rs"] diff --git a/src-tauri/crates/lsp/src/install_pipeline.rs b/src-tauri/crates/lsp/src/install_pipeline.rs index af5016b5c2..cd4b57e1bf 100644 --- a/src-tauri/crates/lsp/src/install_pipeline.rs +++ b/src-tauri/crates/lsp/src/install_pipeline.rs @@ -16,7 +16,7 @@ use std::path::{Path, PathBuf}; use std::process::Stdio; use tokio::process::Command; -use super::commands::discovery::command_exists; +use super::command_detection::command_exists; use super::commands::package_manager::detect_package_manager; use app_paths::lsp_bin_dir; diff --git a/src-tauri/crates/lsp/src/lib.rs b/src-tauri/crates/lsp/src/lib.rs index a42ead9140..9d37322abb 100644 --- a/src-tauri/crates/lsp/src/lib.rs +++ b/src-tauri/crates/lsp/src/lib.rs @@ -7,6 +7,7 @@ pub mod broadcast; pub mod codec; +mod command_detection; pub mod commands; pub mod config; pub mod eslint; diff --git a/src-tauri/crates/lsp/src/lint_tools.rs b/src-tauri/crates/lsp/src/lint_tools.rs index 20c2956d26..5d69b263b5 100644 --- a/src-tauri/crates/lsp/src/lint_tools.rs +++ b/src-tauri/crates/lsp/src/lint_tools.rs @@ -5,6 +5,8 @@ use serde::{Deserialize, Serialize}; use std::process::Command; +use crate::command_detection::command_exists; + /// Information about a lint tool #[derive(Debug, Clone, Serialize, Deserialize)] #[serde(rename_all = "camelCase")] @@ -361,29 +363,6 @@ const LINT_TOOLS: &[LintToolConfig] = &[ }, ]; -/// Check if a command exists in PATH -fn command_exists(cmd: &str) -> bool { - #[cfg(unix)] - { - Command::new("which") - .arg(cmd) - .output() - .map(|output| output.status.success()) - .unwrap_or(false) - } - #[cfg(windows)] - { - let mut command = Command::new("where"); - command.arg(cmd); - // Suppress console window on Windows. - app_platform::hide_console(&mut command); - command - .output() - .map(|output| output.status.success()) - .unwrap_or(false) - } -} - /// Get version of a tool fn get_tool_version(config: &LintToolConfig) -> Option { // Special case for clippy which needs cargo clippy --version diff --git a/src-tauri/crates/lsp/src/workspace_scan/mod.rs b/src-tauri/crates/lsp/src/workspace_scan/mod.rs index 025ccda109..c05daf5dff 100644 --- a/src-tauri/crates/lsp/src/workspace_scan/mod.rs +++ b/src-tauri/crates/lsp/src/workspace_scan/mod.rs @@ -24,8 +24,9 @@ use std::path::Path; use types::{AvailableTool, SingleToolResult}; +use super::command_detection::command_exists; use super::workspace_config::is_lint_tool_enabled; -use process::{command_exists, eslint_available}; +use process::eslint_available; // ============================================ // Helpers for tool detection diff --git a/src-tauri/crates/lsp/src/workspace_scan/orchestrator.rs b/src-tauri/crates/lsp/src/workspace_scan/orchestrator.rs index 398b40421e..af09272528 100644 --- a/src-tauri/crates/lsp/src/workspace_scan/orchestrator.rs +++ b/src-tauri/crates/lsp/src/workspace_scan/orchestrator.rs @@ -21,11 +21,12 @@ use super::clippy; use super::css; use super::eslint; use super::golangci_lint; -use super::process::{command_exists, eslint_available}; +use super::process::eslint_available; use super::python; use super::shell; use super::types::{AvailableTool, SingleToolResult, WorkspaceDiagnostic}; use super::typescript; +use crate::command_detection::command_exists; use crate::workspace_config::is_lint_tool_enabled; // ============================================ diff --git a/src-tauri/crates/lsp/src/workspace_scan/process.rs b/src-tauri/crates/lsp/src/workspace_scan/process.rs index 3e3c187158..8b9393e94d 100644 --- a/src-tauri/crates/lsp/src/workspace_scan/process.rs +++ b/src-tauri/crates/lsp/src/workspace_scan/process.rs @@ -61,34 +61,11 @@ pub fn run_command_with_custom_timeout( } } -/// Check whether a command-line tool is available on the system PATH. -pub fn command_exists(cmd: &str) -> bool { - #[cfg(unix)] - { - Command::new("which") - .arg(cmd) - .output() - .map(|output| output.status.success()) - .unwrap_or(false) - } - #[cfg(windows)] - { - let mut command = Command::new("where"); - command.arg(cmd); - // Suppress console window on Windows. - app_platform::hide_console(&mut command); - command - .output() - .map(|output| output.status.success()) - .unwrap_or(false) - } -} - /// Check whether ESLint is available (local node_modules or global). pub fn eslint_available(workspace_path: &str) -> bool { let local = Path::new(workspace_path) .join("node_modules") .join(".bin") .join("eslint"); - local.exists() || command_exists("eslint") + local.exists() || crate::command_detection::command_exists("eslint") }