дзеркало
https://github.com/l0ng-ai/openidea.git
синхронізовано 2026-11-09 11:15:40 +02:00
On Windows the auto-downloaded jdtls ran through `bin\jdtls.bat`, which starts the Python wrapper and ends with `pause`. When Python failed, `pause` wrote its prompt to stdout in the console code page; the transport treated that as a broken LSP header ("non-UTF-8 header"), dropped it and the server was killed, so the status only said "exited with code 1".
- jdtls is now launched with `java` directly on every platform (Equinox launcher, platform config dir, JDK 24+ flags, forwarded JVM args, `-data`), so Python is no longer needed; user wrappers and unusual layouts keep their command.
- Output outside the protocol on stdout (and non-UTF-8 stderr) is decoded (UTF-8, then the Windows OEM/ANSI code page, then lossy), logged and included in the crash message, for stdio and TCP language servers, DAP adapters and ACP agents.
- Crash messages carry the real exit code and the last lines of output.
未在 Windows 实机验证 (Windows paths covered by unit tests and a Windows-target clippy run).
Fixes #90
Claude-Session: https://claude.ai/code/session_01VUYsdXA2nkF5v6zsC93dpn
68 рядки
2.9 KiB
Rust
68 рядки
2.9 KiB
Rust
//! A stdio server that dies before the handshake: what it printed (on stderr, and
|
|
//! on stdout outside the protocol, in any encoding) must reach the crash message.
|
|
#![cfg(unix)]
|
|
|
|
use ide_core::event::ServerStatus;
|
|
use ide_core::{DocId, IdeEvent, LanguageId};
|
|
use ide_language::{LanguageConfig, LanguageRegistry, LanguageServerConfig};
|
|
use ide_lsp::{LspManager, ProcessLauncher};
|
|
use parking_lot::Mutex;
|
|
use std::sync::Arc;
|
|
use std::time::Duration;
|
|
|
|
/// Shaped like `jdtls.bat` on a Chinese Windows whose Python step fails: an error on
|
|
/// stderr, then `pause` prompting in GBK on stdout (`请按任意键继续. . .`), reading one
|
|
/// key from stdin, exiting with an error.
|
|
const LAUNCHER: &str = r#"#!/bin/sh
|
|
echo "'python' is not recognized as an internal or external command" >&2
|
|
printf '\307\353\260\264\310\316\322\342\274\374\274\314\320\370. . . '
|
|
head -c 1 >/dev/null
|
|
printf '\r\n'
|
|
exit 1
|
|
"#;
|
|
|
|
#[tokio::test(flavor = "multi_thread")]
|
|
async fn crash_before_the_handshake_reports_what_the_server_printed() {
|
|
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 = \"fake\"\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");
|
|
|
|
let crashed = || {
|
|
events.lock().iter().find_map(|e| match e {
|
|
IdeEvent::LanguageServer { status: ServerStatus::Crashed, message, .. } => message.clone(),
|
|
_ => None,
|
|
})
|
|
};
|
|
let mut message = None;
|
|
for _ in 0..1000 {
|
|
message = crashed();
|
|
if message.is_some() {
|
|
break;
|
|
}
|
|
tokio::time::sleep(Duration::from_millis(10)).await;
|
|
}
|
|
let message = message.expect("the server is reported as crashed");
|
|
assert!(message.starts_with("fake exited with code 1:\n"), "{message}");
|
|
assert!(message.contains("'python' is not recognized"), "{message}");
|
|
// The stdout prompt, decoded (code page 936 on Windows, lossily elsewhere).
|
|
assert!(message.contains(". . ."), "{message}");
|
|
mgr.shutdown_all().await;
|
|
}
|