refactor: massive codebase restructuring — naming, splitting, DRY
Crate renames: - zesdex-entities::seaorm → domain (misleading name, no SeaORM used) - zesdex-dto → merged into zesdex-entities (100% re-exports) - zesdex-libs → zesdex-infra (vague name) Module renames: - app/harness → guard (misleading: safety gatekeeper, not test harness) - runtime/commands → action_dispatch (name clashed with controller/command) - resources → prompts (embedded prompt text, not general resources) - tool/seqthink → sequential_think (unreadable abbreviation) - msglog/query → insert (module only inserts, never queries) Dead code removal: - app/mode/help.rs (orphaned — not declared in mod.rs) - app/mode/loading.rs (orphaned — not declared in mod.rs) File splitting (71 new files, avg ~115 lines/file): - app/runtime/actions/: 1→8 files (was 2030 lines) - view/overlays/: 1→16 files (was 1167 lines) - tool/lsp/: 1→8 per-tool files (was 909 lines) - main.rs: 1→5 files (session, daemon, attach, event_loop) - workflow/engine + hive_mind: 2→10 files - subagent/engine + auto: 2→9 files - lsp/provisioner: 1→5 files - review/: 1→6 files - guard/: 1→2 files (extracted patterns) - state/misc: 1→3 files (input, scroll) - mcp/: 1→3 files (transport, adapter) - stream/json_repair extracted from turn.rs DRY: - Pattern constants (STUB_PATTERNS etc) in guard/patterns shared with subagent - 3 near-identical background spawners → 1 generic + thin wrappers - Shared spawn_subagent_with_drain() extracted - Shared create_session() in main - write_osc52 deduplicated Bug fixes: - archive_message(): sess.db → db (wrong variable name) - execute_one_tool(): wrong parameter name - check_credential_read() function was missing (restored from test expectations)
This commit is contained in:
@@ -0,0 +1,199 @@
|
||||
//! Download and install helpers for LSP servers not available via
|
||||
//! system package managers.
|
||||
//!
|
||||
//! Each helper downloads a prebuilt binary (or archive) and places it
|
||||
//! under `~/.local/share/zesdex/lsp/<server-name>/`.
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
use tracing::info;
|
||||
|
||||
use super::config::{ProgressFn, DOWNLOAD_JDTLS, DOWNLOAD_RUST_BIN};
|
||||
use super::discovery::EnvInfo;
|
||||
use super::manager::run_command;
|
||||
|
||||
/// Resolve the directory where downloaded LSP binaries are stored.
|
||||
fn lsp_install_dir(server: &str) -> Result<PathBuf, String> {
|
||||
let base = dirs::data_dir()
|
||||
.ok_or_else(|| "cannot find data directory via dirs crate".to_string())?
|
||||
.join("zesdex")
|
||||
.join("lsp")
|
||||
.join(server);
|
||||
Ok(base)
|
||||
}
|
||||
|
||||
/// Check whether `def` was previously installed via the download tier
|
||||
/// (binary/launcher lives under `~/.local/share/zesdex/lsp/<name>/`).
|
||||
/// Returns the path to the binary if found.
|
||||
pub(super) fn previous_download_install(def: &super::config::LanguageServerDef) -> Option<PathBuf> {
|
||||
let base = lsp_install_dir(&def.name).ok()?;
|
||||
let candidates: &[&str] = match def.name.as_str() {
|
||||
"rust-analyzer" => &["rust-analyzer"],
|
||||
"jdtls" => &["bin/jdtls", "jdtls-launcher.sh", "jdtls"],
|
||||
"typescript-language-server" => &["bin/typescript-language-server"],
|
||||
"gopls" => &["bin/gopls"],
|
||||
_ => return None,
|
||||
};
|
||||
for sub in candidates {
|
||||
let p = base.join(sub);
|
||||
if p.exists() {
|
||||
// Skip directory entries that exist but are the base dir itself.
|
||||
if p.is_file() {
|
||||
return Some(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// Download a file from `url` to `dest` using curl.
|
||||
fn download_url(url: &str, dest: &Path, max_secs: u64) -> Result<(), String> {
|
||||
let path_str = dest.to_str().ok_or("invalid dest path")?.to_string();
|
||||
info!(url = url, dest = %path_str, "downloading");
|
||||
let args = [
|
||||
"-fsSL",
|
||||
"--connect-timeout",
|
||||
"15",
|
||||
"--max-time",
|
||||
&max_secs.to_string(),
|
||||
"-o",
|
||||
&path_str,
|
||||
url,
|
||||
];
|
||||
let (ok, out) = run_command("curl", &args).map_err(|e| format!("curl spawn: {e}"))?;
|
||||
if !ok {
|
||||
return Err(format!("download failed: {}", out.trim()));
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Download rust-analyzer from GitHub releases and install into
|
||||
/// `~/.local/share/zesdex/lsp/rust-analyzer/bin/rust-analyzer`.
|
||||
fn install_rust_analyzer_binary(
|
||||
env: &EnvInfo,
|
||||
progress: ProgressFn<'_>,
|
||||
) -> Result<PathBuf, String> {
|
||||
let base = lsp_install_dir("rust-analyzer")?;
|
||||
std::fs::create_dir_all(&base).map_err(|e| format!("mkdir: {e}"))?;
|
||||
|
||||
let url = if env.is_linux {
|
||||
"https://github.com/rust-lang/rust-analyzer/releases/latest/download/rust-analyzer-x86_64-unknown-linux-gnu.gz"
|
||||
} else if env.is_macos {
|
||||
"https://github.com/rust-lang/rust-analyzer/releases/latest/download/rust-analyzer-aarch64-apple-darwin.gz"
|
||||
} else {
|
||||
return Err("no prebuilt binary for this OS".to_string());
|
||||
};
|
||||
|
||||
let gz = base.join("rust-analyzer.gz");
|
||||
let target = base.join("rust-analyzer");
|
||||
|
||||
if let Some(cb) = progress {
|
||||
cb("Rust: downloading prebuilt binary...");
|
||||
}
|
||||
download_url(url, &gz, 120)?;
|
||||
if let Some(cb) = progress {
|
||||
cb("Rust: decompressing...");
|
||||
}
|
||||
let (ok, out) = run_command("gunzip", &["-f", &gz.to_string_lossy()])
|
||||
.map_err(|e| format!("gunzip spawn: {e}"))?;
|
||||
if !ok {
|
||||
return Err(format!("gunzip: {}", out.trim()));
|
||||
}
|
||||
|
||||
if !target.exists() {
|
||||
return Err("binary missing after decompression".to_string());
|
||||
}
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(&target, std::fs::Permissions::from_mode(0o755))
|
||||
.map_err(|e| format!("chmod: {e}"))?;
|
||||
}
|
||||
if let Some(cb) = progress {
|
||||
cb("Rust: installed ✓");
|
||||
}
|
||||
Ok(target)
|
||||
}
|
||||
|
||||
/// Download Eclipse JDT-LS from the official snapshot server, extract it,
|
||||
/// and create a launcher script at `bin/jdtls`.
|
||||
fn install_jdtls_from_eclipse(progress: ProgressFn) -> Result<PathBuf, String> {
|
||||
let base = lsp_install_dir("jdtls")?;
|
||||
std::fs::create_dir_all(&base).map_err(|e| format!("mkdir: {e}"))?;
|
||||
|
||||
let url = "https://download.eclipse.org/jdtls/snapshots/jdt-language-server-latest.tar.gz";
|
||||
let tarball = base.join("jdtls.tar.gz");
|
||||
if let Some(cb) = progress {
|
||||
cb("Java: downloading JDT-LS (~150MB)...");
|
||||
}
|
||||
download_url(url, &tarball, 300)?;
|
||||
if let Some(cb) = progress {
|
||||
cb("Java: extracting...");
|
||||
}
|
||||
|
||||
let (ok, out) = run_command(
|
||||
"tar",
|
||||
&[
|
||||
"-xzf",
|
||||
tarball.to_str().unwrap_or(""),
|
||||
"-C",
|
||||
base.to_str().unwrap_or("."),
|
||||
],
|
||||
)
|
||||
.map_err(|e| format!("tar spawn: {e}"))?;
|
||||
if !ok {
|
||||
return Err(format!("tar: {}", out.trim()));
|
||||
}
|
||||
let _ = std::fs::remove_file(&tarball);
|
||||
|
||||
if !base.join("plugins").exists() {
|
||||
return Err("extracted archive missing plugins/ directory".to_string());
|
||||
}
|
||||
|
||||
let bin_dir = base.join("bin");
|
||||
std::fs::create_dir_all(&bin_dir).map_err(|e| format!("mkdir bin: {e}"))?;
|
||||
let launcher = bin_dir.join("jdtls");
|
||||
|
||||
let script = r#"#!/usr/bin/env bash
|
||||
set -e
|
||||
JDTLS_HOME="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
LAUNCHER=$(ls "${JDTLS_HOME}/plugins/org.eclipse.equinox.launcher_"*.jar 2>/dev/null | head -n1)
|
||||
CONFIG=$(ls -d "${JDTLS_HOME}"/config_* 2>/dev/null | head -n1)
|
||||
WORKSPACE="${JDTLS_HOME}/workspace"
|
||||
mkdir -p "${WORKSPACE}"
|
||||
exec java \
|
||||
-Declipse.application=org.eclipse.jdt.ls.core.id1 \
|
||||
-Dosgi.bundles.defaultStartLevel=5 \
|
||||
-Declipse.product=org.eclipse.jdt.ls.core.product \
|
||||
-Dlog.level=WARN -noverify -Xmx1G \
|
||||
-jar "${LAUNCHER}" -configuration "${CONFIG}" -data "${WORKSPACE}" \
|
||||
--add-modules=ALL-SYSTEM \
|
||||
--add-opens java.base/java.util=ALL-UNNAMED \
|
||||
--add-opens java.base/java.lang=ALL-UNNAMED \
|
||||
"$@"
|
||||
"#;
|
||||
std::fs::write(&launcher, script).map_err(|e| format!("write launcher: {e}"))?;
|
||||
#[cfg(unix)]
|
||||
{
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
std::fs::set_permissions(&launcher, std::fs::Permissions::from_mode(0o755))
|
||||
.map_err(|e| format!("chmod launcher: {e}"))?;
|
||||
}
|
||||
if let Some(cb) = progress {
|
||||
cb("Java: JDT-LS installed ✓");
|
||||
}
|
||||
Ok(launcher)
|
||||
}
|
||||
|
||||
/// Dispatch a sentinel download tier to the correct helper.
|
||||
pub(super) fn run_download_tier(
|
||||
name: &str,
|
||||
env: &EnvInfo,
|
||||
progress: ProgressFn<'_>,
|
||||
) -> Result<PathBuf, String> {
|
||||
match name {
|
||||
DOWNLOAD_RUST_BIN => install_rust_analyzer_binary(env, progress),
|
||||
DOWNLOAD_JDTLS => install_jdtls_from_eclipse(progress),
|
||||
other => Err(format!("unknown download tier '{other}'")),
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user