fix(lsp): add download-tier helpers, progress callback, and persist previous install check
Auto-install improvements: - Add curl-based download helpers for rust-analyzer and jdtls - Add progress callback threaded through all provision stages - Check ~/.local/share/zesdex/lsp/ for previous downloads so install only runs once, not on every startup - Increase run_command timeout to 180s for large downloads - Add cargo/pacman/brew fallback tiers for Rust - Add pacman/download tiers for Java jdtls Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.8
parent
b20bb95ada
commit
aac1de8af6
+111
-62
@@ -21,6 +21,11 @@ use tracing::{info, warn};
|
|||||||
|
|
||||||
use super::LspManager;
|
use super::LspManager;
|
||||||
|
|
||||||
|
/// Optional progress callback type (non-owning, caller ensures liveness
|
||||||
|
/// for the duration of the provisioning call).
|
||||||
|
/// Intended to be hooked up to a UI toast / status-bar mechanism.
|
||||||
|
pub type ProgressFn<'a> = Option<&'a dyn Fn(&str)>;
|
||||||
|
|
||||||
/// Result of attempting to make a single language server available.
|
/// Result of attempting to make a single language server available.
|
||||||
///
|
///
|
||||||
/// The caller should switch on this variant: AlreadyAvailable and
|
/// The caller should switch on this variant: AlreadyAvailable and
|
||||||
@@ -372,6 +377,30 @@ fn lsp_install_dir(server: &str) -> Result<PathBuf, String> {
|
|||||||
Ok(base)
|
Ok(base)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Check whether `def` was previously installed via the download tier
|
||||||
|
/// (binary/lancher lives under `~/.local/share/zesdex/lsp/<name>/`).
|
||||||
|
/// Returns the path to the binary if found.
|
||||||
|
fn previous_download_install(def: &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.
|
/// Download a file from `url` to `dest` using curl.
|
||||||
fn download_url(url: &str, dest: &Path, max_secs: u64) -> Result<(), String> {
|
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();
|
let path_str = dest.to_str().ok_or("invalid dest path")?.to_string();
|
||||||
@@ -392,7 +421,7 @@ fn download_url(url: &str, dest: &Path, max_secs: u64) -> Result<(), String> {
|
|||||||
|
|
||||||
/// Download rust-analyzer from GitHub releases and install into
|
/// Download rust-analyzer from GitHub releases and install into
|
||||||
/// `~/.local/share/zesdex/lsp/rust-analyzer/bin/rust-analyzer`.
|
/// `~/.local/share/zesdex/lsp/rust-analyzer/bin/rust-analyzer`.
|
||||||
fn install_rust_analyzer_binary(env: &EnvInfo) -> Result<PathBuf, String> {
|
fn install_rust_analyzer_binary(env: &EnvInfo, progress: ProgressFn<'_>) -> Result<PathBuf, String> {
|
||||||
let base = lsp_install_dir("rust-analyzer")?;
|
let base = lsp_install_dir("rust-analyzer")?;
|
||||||
std::fs::create_dir_all(&base).map_err(|e| format!("mkdir: {}", e))?;
|
std::fs::create_dir_all(&base).map_err(|e| format!("mkdir: {}", e))?;
|
||||||
|
|
||||||
@@ -407,7 +436,9 @@ fn install_rust_analyzer_binary(env: &EnvInfo) -> Result<PathBuf, String> {
|
|||||||
let gz = base.join("rust-analyzer.gz");
|
let gz = base.join("rust-analyzer.gz");
|
||||||
let target = base.join("rust-analyzer");
|
let target = base.join("rust-analyzer");
|
||||||
|
|
||||||
|
if let Some(cb) = progress { cb("Rust: downloading prebuilt binary..."); }
|
||||||
download_url(url, &gz, 120)?;
|
download_url(url, &gz, 120)?;
|
||||||
|
if let Some(cb) = progress { cb("Rust: decompressing..."); }
|
||||||
let (ok, out) = run_command("gunzip", &["-f", &gz.to_string_lossy()])
|
let (ok, out) = run_command("gunzip", &["-f", &gz.to_string_lossy()])
|
||||||
.map_err(|e| format!("gunzip spawn: {}", e))?;
|
.map_err(|e| format!("gunzip spawn: {}", e))?;
|
||||||
if !ok {
|
if !ok {
|
||||||
@@ -423,18 +454,21 @@ fn install_rust_analyzer_binary(env: &EnvInfo) -> Result<PathBuf, String> {
|
|||||||
std::fs::set_permissions(&target, std::fs::Permissions::from_mode(0o755))
|
std::fs::set_permissions(&target, std::fs::Permissions::from_mode(0o755))
|
||||||
.map_err(|e| format!("chmod: {}", e))?;
|
.map_err(|e| format!("chmod: {}", e))?;
|
||||||
}
|
}
|
||||||
|
if let Some(cb) = progress { cb("Rust: installed ✓"); }
|
||||||
Ok(target)
|
Ok(target)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Download Eclipse JDT-LS from the official snapshot server, extract it,
|
/// Download Eclipse JDT-LS from the official snapshot server, extract it,
|
||||||
/// and create a launcher script at `bin/jdtls`.
|
/// and create a launcher script at `bin/jdtls`.
|
||||||
fn install_jdtls_from_eclipse() -> Result<PathBuf, String> {
|
fn install_jdtls_from_eclipse(progress: ProgressFn) -> Result<PathBuf, String> {
|
||||||
let base = lsp_install_dir("jdtls")?;
|
let base = lsp_install_dir("jdtls")?;
|
||||||
std::fs::create_dir_all(&base).map_err(|e| format!("mkdir: {}", e))?;
|
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 url = "https://download.eclipse.org/jdtls/snapshots/jdt-language-server-latest.tar.gz";
|
||||||
let tarball = base.join("jdtls.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)?;
|
download_url(url, &tarball, 300)?;
|
||||||
|
if let Some(cb) = progress { cb("Java: extracting..."); }
|
||||||
|
|
||||||
let (ok, out) = run_command("tar", &[
|
let (ok, out) = run_command("tar", &[
|
||||||
"-xzf", tarball.to_str().unwrap_or(""),
|
"-xzf", tarball.to_str().unwrap_or(""),
|
||||||
@@ -478,14 +512,15 @@ exec java \
|
|||||||
std::fs::set_permissions(&launcher, std::fs::Permissions::from_mode(0o755))
|
std::fs::set_permissions(&launcher, std::fs::Permissions::from_mode(0o755))
|
||||||
.map_err(|e| format!("chmod launcher: {}", e))?;
|
.map_err(|e| format!("chmod launcher: {}", e))?;
|
||||||
}
|
}
|
||||||
|
if let Some(cb) = progress { cb("Java: JDT-LS installed ✓"); }
|
||||||
Ok(launcher)
|
Ok(launcher)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Dispatch a sentinel download tier to the correct helper.
|
/// Dispatch a sentinel download tier to the correct helper.
|
||||||
fn run_download_tier(name: &str, env: &EnvInfo) -> Result<PathBuf, String> {
|
fn run_download_tier(name: &str, env: &EnvInfo, progress: ProgressFn<'_>) -> Result<PathBuf, String> {
|
||||||
match name {
|
match name {
|
||||||
DOWNLOAD_RUST_BIN => install_rust_analyzer_binary(env),
|
DOWNLOAD_RUST_BIN => install_rust_analyzer_binary(env, progress),
|
||||||
DOWNLOAD_JDTLS => install_jdtls_from_eclipse(),
|
DOWNLOAD_JDTLS => install_jdtls_from_eclipse(progress),
|
||||||
other => Err(format!("unknown download tier '{}'", other)),
|
other => Err(format!("unknown download tier '{}'", other)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -533,10 +568,16 @@ fn manual_instructions(def: &LanguageServerDef) -> String {
|
|||||||
/// can exit 0 even if the binary wasn't actually placed on PATH (rare,
|
/// can exit 0 even if the binary wasn't actually placed on PATH (rare,
|
||||||
/// but happens with broken rustup installs). Re-checking gives us a
|
/// but happens with broken rustup installs). Re-checking gives us a
|
||||||
/// real signal rather than trusting the exit code alone.
|
/// real signal rather than trusting the exit code alone.
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn provision_single(def: &LanguageServerDef, env: &EnvInfo) -> ProvisionResult {
|
pub fn provision_single(def: &LanguageServerDef, env: &EnvInfo) -> ProvisionResult {
|
||||||
// Already installed?
|
provision_single_with_progress(def, env, None)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn provision_single_with_progress(def: &LanguageServerDef, env: &EnvInfo, progress: ProgressFn<'_>) -> ProvisionResult {
|
||||||
|
// 1. Check PATH.
|
||||||
for bin in &def.binary_names {
|
for bin in &def.binary_names {
|
||||||
if let Some(path) = which(bin) {
|
if let Some(path) = which(bin) {
|
||||||
|
if let Some(cb) = progress { cb(&format!("{}: already installed (PATH)", def.language)); }
|
||||||
return ProvisionResult::AlreadyAvailable {
|
return ProvisionResult::AlreadyAvailable {
|
||||||
server_name: def.name.clone(),
|
server_name: def.name.clone(),
|
||||||
language: def.language.clone(),
|
language: def.language.clone(),
|
||||||
@@ -545,38 +586,46 @@ pub fn provision_single(def: &LanguageServerDef, env: &EnvInfo) -> ProvisionResu
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 2. Check download-install directory (~/.local/share/zesdex/lsp/<name>/...).
|
||||||
|
if let Some(path) = previous_download_install(def) {
|
||||||
|
if let Some(cb) = progress { cb(&format!("{}: found previous install", def.language)); }
|
||||||
|
return ProvisionResult::AlreadyAvailable {
|
||||||
|
server_name: def.name.clone(),
|
||||||
|
language: def.language.clone(),
|
||||||
|
binary_path: path.to_string_lossy().to_string(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(cb) = progress { cb(&format!("{}: checking install options...", def.language)); }
|
||||||
|
|
||||||
let mut last_reason = String::from("no install tiers succeeded");
|
let mut last_reason = String::from("no install tiers succeeded");
|
||||||
|
|
||||||
for tier in &def.install_tiers {
|
for tier in &def.install_tiers {
|
||||||
// Prerequisite gating: skip tiers whose required tools are missing.
|
// Prerequisite gating
|
||||||
let prereqs_met = tier.requires.iter().all(|req| match req.as_str() {
|
let prereqs_met = tier.requires.iter().all(|req| match req.as_str() {
|
||||||
"rustup" => env.has_rustup,
|
"rustup" => env.has_rustup, "npm" => env.has_npm,
|
||||||
"npm" => env.has_npm,
|
"go" => env.has_go, "java" => env.has_java,
|
||||||
"go" => env.has_go,
|
"cargo" => env.has_cargo, "curl" => env.has_curl,
|
||||||
"java" => env.has_java,
|
"tar" => env.has_tar, "pacman" => env.has_pacman,
|
||||||
"apt" => env.has_apt,
|
"apt" => env.has_apt, "brew" => env.has_brew,
|
||||||
"brew" => env.has_brew,
|
"dnf" => env.has_dnf, _ => which(req).is_some(),
|
||||||
_ => which(req).is_some(),
|
|
||||||
});
|
});
|
||||||
if !prereqs_met {
|
if !prereqs_met {
|
||||||
last_reason = format!(
|
let skip = format!("{}: {} — missing prerequisite", def.language, tier.label);
|
||||||
"tier '{}' skipped: missing prerequisite (one of: {})",
|
if let Some(cb) = progress { cb(&skip); }
|
||||||
tier.label,
|
last_reason = format!("tier '{}' skipped: missing prerequisite", tier.label);
|
||||||
tier.requires.join(", ")
|
warn!(server = %def.name, tier = %tier.label, "skipped — missing prerequisites");
|
||||||
);
|
|
||||||
warn!(
|
|
||||||
server = %def.name,
|
|
||||||
tier = %tier.label,
|
|
||||||
"install tier skipped — missing prerequisites"
|
|
||||||
);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If command is a download sentinel, dispatch to helper.
|
let trying = format!("{}: {}...", def.language, tier.label);
|
||||||
|
if let Some(cb) = progress { cb(&trying); }
|
||||||
|
|
||||||
|
// Download sentinel → helper.
|
||||||
if tier.command.starts_with("__download_") && tier.command.ends_with("__") {
|
if tier.command.starts_with("__download_") && tier.command.ends_with("__") {
|
||||||
match run_download_tier(&tier.command, env) {
|
match run_download_tier(&tier.command, env, progress) {
|
||||||
Ok(path) => {
|
Ok(path) => {
|
||||||
info!(server = %def.name, tier = %tier.label, binary = %path.display(), "download install succeeded");
|
info!(server = %def.name, tier = %tier.label, binary = %path.display(), "installed");
|
||||||
return ProvisionResult::Installed {
|
return ProvisionResult::Installed {
|
||||||
server_name: def.name.clone(),
|
server_name: def.name.clone(),
|
||||||
language: def.language.clone(),
|
language: def.language.clone(),
|
||||||
@@ -585,72 +634,49 @@ pub fn provision_single(def: &LanguageServerDef, env: &EnvInfo) -> ProvisionResu
|
|||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
last_reason = format!("tier '{}' failed: {}", tier.label, e);
|
last_reason = format!("tier '{}' failed: {}", tier.label, e);
|
||||||
warn!(server = %def.name, tier = %tier.label, error = %e, "download install failed");
|
warn!(server = %def.name, tier = %tier.label, error = %e, "download failed");
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Normal tier: shell out.
|
// Normal shell-out tier.
|
||||||
let arg_refs: Vec<&str> = tier.args.iter().map(|s| s.as_str()).collect();
|
let arg_refs: Vec<&str> = tier.args.iter().map(|s| s.as_str()).collect();
|
||||||
match run_command(&tier.command, &arg_refs) {
|
match run_command(&tier.command, &arg_refs) {
|
||||||
Ok((true, _)) => {
|
Ok((true, _)) => {
|
||||||
// Verify the binary is now actually reachable.
|
|
||||||
let located = def
|
let located = def
|
||||||
.binary_names
|
.binary_names
|
||||||
.iter()
|
.iter()
|
||||||
.find_map(|b| which(b).map(|p| p.to_string_lossy().to_string()));
|
.find_map(|b| which(b).map(|p| p.to_string_lossy().to_string()));
|
||||||
if let Some(path) = located {
|
if let Some(path) = located {
|
||||||
info!(
|
if let Some(cb) = progress { cb(&format!("{}: installed ✓", def.language)); }
|
||||||
server = %def.name,
|
info!(server = %def.name, tier = %tier.label, binary = %path, "installed");
|
||||||
tier = %tier.label,
|
|
||||||
binary = %path,
|
|
||||||
"installed language server"
|
|
||||||
);
|
|
||||||
return ProvisionResult::Installed {
|
return ProvisionResult::Installed {
|
||||||
server_name: def.name.clone(),
|
server_name: def.name.clone(),
|
||||||
language: def.language.clone(),
|
language: def.language.clone(),
|
||||||
binary_path: path,
|
binary_path: path,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
last_reason = format!(
|
last_reason = format!("tier '{}' exited 0 but binary not on PATH", tier.label);
|
||||||
"tier '{}' exited 0 but '{}' not found on PATH afterwards",
|
warn!(server = %def.name, tier = %tier.label, "success reported but binary missing");
|
||||||
tier.label, tier.command
|
|
||||||
);
|
|
||||||
warn!(
|
|
||||||
server = %def.name,
|
|
||||||
tier = %tier.label,
|
|
||||||
"install command reported success but binary missing"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Ok((false, out)) => {
|
Ok((false, out)) => {
|
||||||
let trimmed = out.trim();
|
let trimmed = out.trim();
|
||||||
last_reason = format!("tier '{}' failed: {}", tier.label, trimmed);
|
let snippet: String = trimmed.chars().take(300).collect();
|
||||||
warn!(
|
last_reason = format!("tier '{}' failed: {}", tier.label, snippet);
|
||||||
server = %def.name,
|
warn!(server = %def.name, tier = %tier.label, output = %snippet, "failed");
|
||||||
tier = %tier.label,
|
|
||||||
output = trimmed,
|
|
||||||
"install command failed"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
last_reason = format!("tier '{}' error: {}", tier.label, e);
|
last_reason = format!("tier '{}' error: {}", tier.label, e);
|
||||||
warn!(
|
warn!(server = %def.name, tier = %tier.label, error = %e, "errored");
|
||||||
server = %def.name,
|
|
||||||
tier = %tier.label,
|
|
||||||
error = %e,
|
|
||||||
"install command errored"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let manual = manual_instructions(def);
|
let manual = manual_instructions(def);
|
||||||
ProvisionResult::Failed {
|
ProvisionResult::Failed {
|
||||||
language: def.language.clone(),
|
language: def.language.clone(), server_name: def.name.clone(),
|
||||||
server_name: def.name.clone(),
|
reason: last_reason, manual_instructions: manual,
|
||||||
reason: last_reason,
|
|
||||||
manual_instructions: manual,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -660,6 +686,7 @@ pub fn provision_single(def: &LanguageServerDef, env: &EnvInfo) -> ProvisionResu
|
|||||||
/// Flow: detect_env() once → for each server in supported_servers()
|
/// Flow: detect_env() once → for each server in supported_servers()
|
||||||
/// call provision_single() → collect results. Order matches
|
/// call provision_single() → collect results. Order matches
|
||||||
/// supported_servers() (rust, typescript, go, java).
|
/// supported_servers() (rust, typescript, go, java).
|
||||||
|
#[allow(dead_code)]
|
||||||
pub fn provision_all() -> Vec<ProvisionResult> {
|
pub fn provision_all() -> Vec<ProvisionResult> {
|
||||||
let env = detect_env();
|
let env = detect_env();
|
||||||
info!(
|
info!(
|
||||||
@@ -684,6 +711,28 @@ pub fn provision_all() -> Vec<ProvisionResult> {
|
|||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Like `provision_all` but calls `progress` with a human-readable status
|
||||||
|
/// string at each stage of each server's install attempt.
|
||||||
|
pub fn provision_all_with_progress(progress: ProgressFn) -> Vec<ProvisionResult> {
|
||||||
|
let env = detect_env();
|
||||||
|
if let Some(cb) = progress {
|
||||||
|
let flags = [
|
||||||
|
("rustup", env.has_rustup), ("cargo", env.has_cargo),
|
||||||
|
("npm", env.has_npm), ("go", env.has_go), ("java", env.has_java),
|
||||||
|
("curl", env.has_curl), ("tar", env.has_tar),
|
||||||
|
("pacman", env.has_pacman), ("apt", env.has_apt), ("brew", env.has_brew),
|
||||||
|
];
|
||||||
|
let avail: String = flags.iter()
|
||||||
|
.filter(|(_, v)| *v).map(|(k, _)| *k)
|
||||||
|
.collect::<Vec<_>>().join(", ");
|
||||||
|
cb(&format!("LSP: environment ready — {}", avail));
|
||||||
|
}
|
||||||
|
supported_servers()
|
||||||
|
.iter()
|
||||||
|
.map(|def| provision_single_with_progress(def, &env, progress))
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
/// For every successful provision result, attach the corresponding
|
/// For every successful provision result, attach the corresponding
|
||||||
/// server to the given `LspManager`.
|
/// server to the given `LspManager`.
|
||||||
///
|
///
|
||||||
|
|||||||
+14
-9
@@ -153,30 +153,35 @@ impl AppStateRest {
|
|||||||
std::thread::spawn(move || {
|
std::thread::spawn(move || {
|
||||||
use crate::app::lsp::provisioner::{self, ProvisionResult};
|
use crate::app::lsp::provisioner::{self, ProvisionResult};
|
||||||
|
|
||||||
fn push_msg(q: &Arc<Mutex<VecDeque<String>>>, msg: String) {
|
fn push_msg(q: &Arc<Mutex<VecDeque<String>>>, msg: &str) {
|
||||||
if let Ok(mut q) = q.lock() {
|
if let Ok(mut q) = q.lock() {
|
||||||
q.push_back(msg);
|
q.push_back(msg.to_string());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
push_msg(&msg_queue, "LSP: provisioning servers...".to_string());
|
// Wrap the msg_queue in a static-lifetime closure for use as ProgressFn.
|
||||||
let results = provisioner::provision_all();
|
let progress: provisioner::ProgressFn = Some(&|msg: &str| push_msg(&msg_queue, msg));
|
||||||
push_msg(&msg_queue, "LSP: connecting servers...".to_string());
|
|
||||||
|
let report = |msg: &str| { if let Some(f) = &progress { f(msg); }};
|
||||||
|
|
||||||
|
report("LSP: provisioning servers...");
|
||||||
|
let results = provisioner::provision_all_with_progress(progress);
|
||||||
|
report("LSP: connecting servers...");
|
||||||
let connected = provisioner::auto_connect(&lsp_mgr, &results);
|
let connected = provisioner::auto_connect(&lsp_mgr, &results);
|
||||||
for name in &connected {
|
for name in &connected {
|
||||||
tracing::info!("LSP: {} connected", name);
|
tracing::info!("LSP: {} connected", name);
|
||||||
push_msg(&msg_queue, format!("LSP: {} connected ✓", name));
|
let m = format!("LSP: {} connected ✓", name); push_msg(&msg_queue, &m);
|
||||||
}
|
}
|
||||||
for r in &results {
|
for r in &results {
|
||||||
if let ProvisionResult::Failed { language, server_name, reason, .. } = r {
|
if let ProvisionResult::Failed { language, server_name, reason, .. } = r {
|
||||||
tracing::warn!("LSP {} ({}): {}", server_name, language, reason);
|
tracing::warn!("LSP {} ({}): {}", server_name, language, reason);
|
||||||
push_msg(&msg_queue, format!("LSP: {} ({}) ✗ - {}", server_name, language, reason));
|
let m = format!("LSP: {} ({}) ✗ - {}", server_name, language, reason); push_msg(&msg_queue, &m);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if connected.is_empty() {
|
if connected.is_empty() {
|
||||||
push_msg(&msg_queue, "LSP: no servers available — install manually or check prerequisites".to_string());
|
let m = "LSP: no servers available — install manually or check prerequisites".to_string(); push_msg(&msg_queue, &m);
|
||||||
} else {
|
} else {
|
||||||
push_msg(&msg_queue, format!("LSP: {} server(s) connected", connected.len()));
|
let m = format!("LSP: {} server(s) connected", connected.len()); push_msg(&msg_queue, &m);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user