From 8dc1ee062f88c5dfe213d4ac247911530fa0067a Mon Sep 17 00:00:00 2001 From: l0ng-ai <24760907+l0ng-ai@users.noreply.github.com> Date: Sat, 31 Oct 2026 03:49:20 +0800 Subject: [PATCH] fix(java): show why jdtls crashed when a run needs it A run that needed a crashed jdtls reported only "jdtls is not ready (Crashed)", although the server's exit code and last output were already recorded for its status. The run console and notification now show them. --- crates/ide-lsp/src/jdtls.rs | 2 +- crates/ide-lsp/tests/stdio_crash.rs | 39 ++++++++++++++++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/ide-lsp/src/jdtls.rs b/crates/ide-lsp/src/jdtls.rs index ef883f9c..3e4698b0 100644 --- a/crates/ide-lsp/src/jdtls.rs +++ b/crates/ide-lsp/src/jdtls.rs @@ -192,7 +192,7 @@ impl LspManager { ide_core::event::ServerStatus::Missing => { "jdtls is not installed (Settings › Languages & Frameworks › Language Servers)".to_owned() } - ide_core::event::ServerStatus::Misconfigured => { + ide_core::event::ServerStatus::Misconfigured | ide_core::event::ServerStatus::Crashed => { server.status_message().unwrap_or_else(|| "jdtls cannot start".to_owned()) } _ => format!("jdtls is not ready ({status:?})"), diff --git a/crates/ide-lsp/tests/stdio_crash.rs b/crates/ide-lsp/tests/stdio_crash.rs index 37583022..4bc4ec8c 100644 --- a/crates/ide-lsp/tests/stdio_crash.rs +++ b/crates/ide-lsp/tests/stdio_crash.rs @@ -3,7 +3,7 @@ #![cfg(unix)] use ide_core::event::ServerStatus; -use ide_core::{DocId, IdeEvent, LanguageId}; +use ide_core::{CancellationToken, DocId, IdeEvent, LanguageId}; use ide_language::{LanguageConfig, LanguageRegistry, LanguageServerConfig}; use ide_lsp::{LspManager, ProcessLauncher}; use parking_lot::Mutex; @@ -65,3 +65,40 @@ async fn crash_before_the_handshake_reports_what_the_server_printed() { assert!(message.contains(". . ."), "{message}"); mgr.shutdown_all().await; } + +#[tokio::test(flavor = "multi_thread")] +async fn a_request_that_needs_a_crashed_jdtls_reports_why_it_crashed() { + let dir = tempfile::tempdir().unwrap(); + let root = dir.path().to_path_buf(); + let script = root.join("fake-jdtls"); + std::fs::write(&script, LAUNCHER).unwrap(); + std::fs::set_permissions(&script, std::os::unix::fs::PermissionsExt::from_mode(0o755)).unwrap(); + + let server: LanguageServerConfig = + toml::from_str(&format!("name = \"jdtls\"\ncommand = {:?}", script.to_string_lossy())).unwrap(); + let lang: LanguageConfig = toml::from_str("id = \"java\"\nname = \"Java\"\nextensions = [\"java\"]").unwrap(); + let lang = LanguageConfig { language_servers: vec![server], ..lang }; + let registry = Arc::new(LanguageRegistry::from_configs(vec![lang])); + let events = Arc::new(Mutex::new(Vec::new())); + let sink = { + let events = events.clone(); + Arc::new(move |e: IdeEvent| events.lock().push(e)) + }; + let mgr = LspManager::with_launcher(root.clone(), registry, sink, Arc::new(ProcessLauncher)); + let path = root.join("A.java"); + std::fs::write(&path, "class A {}\n").unwrap(); + mgr.did_open(DocId::next(), &path, &LanguageId::new("java"), 1, "class A {}\n"); + for _ in 0..1000 { + let crashed = + events.lock().iter().any(|e| matches!(e, IdeEvent::LanguageServer { status: ServerStatus::Crashed, .. })); + if crashed { + break; + } + tokio::time::sleep(Duration::from_millis(10)).await; + } + + let error = mgr.java_classpath(&path, false, &CancellationToken::new()).await.unwrap_err(); + assert!(error.message.contains("jdtls exited with code 1"), "{}", error.message); + assert!(error.message.contains("'python' is not recognized"), "{}", error.message); + mgr.shutdown_all().await; +}