mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-07 02:06:05 -10:00
feat(java): add kmp-lsp as an alternative language server (#1891)
This commit is contained in:
9 files changed
+149
No files matched your search
@@ -78,5 +78,6 @@ the [official plugins](plugins/official). The plugin API is still a preview.
|
||||
|
||||
Database tool window notes, including Oracle Instant Client setup, are in
|
||||
[`docs/databases.md`](docs/databases.md).
|
||||
Java language server choices and their capabilities are in [`docs/java.md`](docs/java.md).
|
||||
|
||||
<sub>Not affiliated with JetBrains. IntelliJ IDEA is a trademark of JetBrains s.r.o.</sub>
|
||||
@@ -639,6 +639,7 @@ mod tests {
|
||||
for (lang, servers) in [
|
||||
("python", vec!["basedpyright", "pyright", "ty", "pyrefly"]),
|
||||
("typescript", vec!["vtsls", "typescript-language-server"]),
|
||||
("java", vec!["jdtls", "kmp-lsp"]),
|
||||
] {
|
||||
let cfg = reg.get(&LanguageId::new(lang)).unwrap();
|
||||
let got: Vec<(&str, bool)> = cfg
|
||||
|
||||
@@ -1673,6 +1673,18 @@ async fn library_classes_in_jars_open_as_their_source_or_decompiled() {
|
||||
assert_eq!(asked[0], json!({ "command": "decompile", "arguments": [thing] }));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn alternative_java_server_uses_lsp_without_jdt_commands() {
|
||||
let h = harness_for("java", "java", &[("kmp-lsp", incremental_caps())], &[]);
|
||||
let (doc, path) = h.open("Sample.java", "class Sample {}");
|
||||
h.wait_status("kmp-lsp", ServerStatus::Ready).await;
|
||||
assert!(h.mgr.hover(doc, Pos::new(0, 7), Default::default()).await.is_ok());
|
||||
let error = h.mgr.java_debug_server(&path).await.unwrap_err();
|
||||
assert_eq!(error.kind, ide_core::error::ErrorKind::Unavailable);
|
||||
assert!(error.message.contains("jdtls"));
|
||||
assert!(h.launcher.state("kmp-lsp").requested("workspace/executeCommand").is_empty());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn fallback_servers_only_run_when_the_preferred_one_is_missing() {
|
||||
// Preferred installed: the alternative is never started.
|
||||
|
||||
@@ -63,6 +63,55 @@ fn lang(id: &str, ext: &str, server: LanguageServerConfig) -> LanguageConfig {
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[ignore = "requires kmp-lsp on PATH (or OPENIDEA_TEST_KMP_LSP)"]
|
||||
async fn kmp_lsp_java_end_to_end() {
|
||||
let dir = tempfile::tempdir().unwrap();
|
||||
let root = dir.path().canonicalize().unwrap();
|
||||
let path = root.join("Sample.java");
|
||||
let source = "class Sample {\n static String greet() { return \"Hello\"; }\n void run() { greet(); }\n}\n";
|
||||
std::fs::write(&path, source).unwrap();
|
||||
let mut java = LanguageRegistry::load(None).unwrap().get(&LanguageId::new("java")).unwrap();
|
||||
java.language_servers.retain(|s| s.name == "kmp-lsp");
|
||||
assert_eq!(java.language_servers.len(), 1);
|
||||
if let Ok(bin) = std::env::var("OPENIDEA_TEST_KMP_LSP") {
|
||||
java.language_servers[0].command = bin;
|
||||
}
|
||||
let (mgr, events) = manager(&root, LanguageRegistry::from_configs(vec![java]));
|
||||
let doc = DocId::next();
|
||||
mgr.did_open(doc, &path, &LanguageId::new("java"), 1, source);
|
||||
retry("kmp-lsp ready", Duration::from_secs(30), || {
|
||||
let status = last_status(&events, "kmp-lsp");
|
||||
if let Some((ServerStatus::Crashed | ServerStatus::Missing | ServerStatus::Misconfigured, msg)) = &status {
|
||||
panic!("kmp-lsp failed: {msg:?}");
|
||||
}
|
||||
async move { matches!(status, Some((ServerStatus::Ready | ServerStatus::Busy, _))).then_some(()) }
|
||||
})
|
||||
.await;
|
||||
let hover = retry("Java hover", Duration::from_secs(30), || async {
|
||||
mgr.hover(doc, Pos::new(2, 18), Default::default()).await.ok().flatten()
|
||||
})
|
||||
.await;
|
||||
assert!(hover.contents.value.contains("greet"), "{}", hover.contents.value);
|
||||
let defs = retry("Java definition", Duration::from_secs(30), || async {
|
||||
mgr.goto(doc, Pos::new(2, 18), GotoKind::Definition).await.ok().filter(|d| !d.is_empty())
|
||||
})
|
||||
.await;
|
||||
assert_eq!(defs[0].path, path);
|
||||
assert_eq!(defs[0].range.start.line, 1);
|
||||
retry("Java completion", Duration::from_secs(30), || async {
|
||||
let list = mgr.completion(doc, Pos::new(2, 20), None, Default::default()).await.ok()?;
|
||||
list.items.iter().any(|i| i.label.starts_with("greet")).then_some(())
|
||||
})
|
||||
.await;
|
||||
let tokens = mgr.semantic_tokens(doc).await.unwrap().unwrap();
|
||||
assert!(!tokens.data.is_empty() && tokens.data.len() % 5 == 0);
|
||||
let error = mgr.java_debug_server(&path).await.unwrap_err();
|
||||
assert_eq!(error.kind, ide_core::error::ErrorKind::Unavailable);
|
||||
assert!(error.message.contains("jdtls"));
|
||||
tokio::time::timeout(Duration::from_secs(10), mgr.shutdown_all()).await.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
#[ignore = "requires jdtls on PATH and a compatible JDK"]
|
||||
async fn intellij_java_formatter_preserves_manual_breaks_and_wraps_long_lines() {
|
||||
|
||||
@@ -151,6 +151,36 @@ mod tests {
|
||||
assert_eq!(select_asset(&names, rules, WIN), Some(names[4]));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn kmp_lsp_assets_include_the_native_jar_indexer() {
|
||||
let names = [
|
||||
"kmp-lsp-darwin-aarch64.gz",
|
||||
"kmp-lsp-darwin-aarch64.tar.gz",
|
||||
"kmp-lsp-darwin-x86_64.tar.gz",
|
||||
"kmp-lsp-linux-aarch64.tar.gz",
|
||||
"kmp-lsp-linux-x86_64.tar.gz",
|
||||
"kmp-lsp-windows-x86_64.zip",
|
||||
"kmp-lsp-windows-aarch64.zip",
|
||||
"kmp-lsp-v0.27.0.vsix",
|
||||
"kmp-lsp-win32-x64-v0.27.0.vsix",
|
||||
"kmp-lsp-win32-arm64-v0.27.0.vsix",
|
||||
];
|
||||
let rules = crate::registry::find("kmp-lsp").unwrap().asset_rules();
|
||||
for (os, arch, expected) in [
|
||||
(Os::Mac, Arch::Arm64, names[1]),
|
||||
(Os::Mac, Arch::X64, names[2]),
|
||||
(Os::Linux, Arch::Arm64, names[3]),
|
||||
(Os::Linux, Arch::X64, names[4]),
|
||||
(Os::Windows, Arch::X64, names[8]),
|
||||
(Os::Windows, Arch::Arm64, names[9]),
|
||||
] {
|
||||
assert_eq!(select_asset(&names, rules, Platform { os, arch }), Some(expected));
|
||||
}
|
||||
// The plain gzip, Windows zip and universal VSIX omit the sidecar or server.
|
||||
assert_eq!(select_asset(&[names[0]], rules, MAC_ARM), None);
|
||||
assert_eq!(select_asset(&names[5..8], rules, WIN), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rosetta_fallback_and_missing() {
|
||||
let names = ["zls-x86_64-macos.tar.xz", "zls-x86_64-linux.tar.xz", "zls-x86_64-windows.zip"];
|
||||
|
||||
@@ -618,6 +618,30 @@ pub static TOOLS: &[ToolSpec] = &[
|
||||
homepage: "https://github.com/eclipse-jdtls/eclipse.jdt.ls",
|
||||
needs: &[],
|
||||
},
|
||||
ToolSpec {
|
||||
name: "kmp-lsp",
|
||||
display: "kmp-lsp",
|
||||
kind: LanguageServer,
|
||||
commands: &["kmp-lsp"],
|
||||
method: Method::Release {
|
||||
source: ReleaseSource::GitHub { repo: "Hessesian/kmp-lsp" },
|
||||
// These distributions include the native JAR indexer next to the server.
|
||||
// The single-binary .gz and Windows .zip assets do not.
|
||||
assets: &[
|
||||
rule(Mac, Arm64, "kmp-lsp-darwin-aarch64.tar.gz"),
|
||||
rule(Mac, X64, "kmp-lsp-darwin-x86_64.tar.gz"),
|
||||
rule(Linux, Arm64, "kmp-lsp-linux-aarch64.tar.gz"),
|
||||
rule(Linux, X64, "kmp-lsp-linux-x86_64.tar.gz"),
|
||||
rule(Windows, Arm64, "kmp-lsp-win32-arm64-*.vsix"),
|
||||
rule(Windows, X64, "kmp-lsp-win32-x64-*.vsix"),
|
||||
],
|
||||
locate: &["kmp-lsp"],
|
||||
},
|
||||
fallback: None,
|
||||
version: ver("kmp-lsp"),
|
||||
homepage: "https://github.com/Hessesian/kmp-lsp",
|
||||
needs: &[],
|
||||
},
|
||||
ToolSpec {
|
||||
name: "java-debug",
|
||||
display: "Java Debugger (Microsoft java-debug)",
|
||||
|
||||
@@ -170,6 +170,10 @@ fn interchangeable_servers_list_their_alternatives() {
|
||||
assert_eq!(find(&list, name).alternatives, python);
|
||||
}
|
||||
assert_eq!(find(&list, "vtsls").alternatives, ["vtsls", "typescript-language-server"]);
|
||||
for name in ["jdtls", "kmp-lsp"] {
|
||||
assert_eq!(find(&list, name).alternatives, ["jdtls", "kmp-lsp"]);
|
||||
assert_eq!(find(&list, name).languages, ["Java"]);
|
||||
}
|
||||
// A regular server after a fallback starts no group with it.
|
||||
assert!(find(&list, "angular-language-server").alternatives.is_empty());
|
||||
assert!(find(&list, "ruff").alternatives.is_empty());
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
# Java language servers
|
||||
|
||||
Settings › Languages & Frameworks › Language Servers lists Eclipse JDT Language
|
||||
Server (`jdtls`) and [kmp-lsp](https://github.com/Hessesian/kmp-lsp). Select either
|
||||
tool and use **Language server for Java** to choose one. The choice can be global
|
||||
or limited to the current project. **Automatic** prefers jdtls and falls back to
|
||||
kmp-lsp when jdtls is not installed; **None** disables both.
|
||||
|
||||
Install kmp-lsp from that page, or point its executable path at an existing
|
||||
installation. The IDE downloads a platform distribution containing both kmp-lsp
|
||||
and its native JAR indexer. Reinstall and uninstall manage both together.
|
||||
|
||||
kmp-lsp offers syntax-based Java code insight without running a JVM. It does not
|
||||
provide JDT's compiler analysis, Lombok integration, or JDT-specific refactorings.
|
||||
Java Application builds, classpath discovery and the Microsoft Java debugger still
|
||||
require jdtls; use jdtls for those flows. Maven and Gradle build tasks can run
|
||||
independently of the selected code-insight server.
|
||||
|
||||
This built-in choice applies to Java. Kotlin and Swift keep their existing server
|
||||
choices. To use another server, add it with **New Language Server**, assign its
|
||||
languages, and disable the servers it replaces.
|
||||
@@ -58,3 +58,10 @@ generatesMetadataFilesAtProjectRoot = false
|
||||
exclusions = ["**/node_modules/**", "**/.metadata/**", "**/archetype-resources/**", "**/META-INF/maven/**"]
|
||||
maven = { enabled = true }
|
||||
gradle = { enabled = true, wrapper = { enabled = true } }
|
||||
|
||||
[[language-servers]]
|
||||
name = "kmp-lsp"
|
||||
fallback = true
|
||||
command = "kmp-lsp"
|
||||
root-markers = ["pom.xml", "build.gradle", "build.gradle.kts", "settings.gradle", "settings.gradle.kts", "build.xml", ".project"]
|
||||
install-hint = "Install kmp-lsp in Settings › Languages & Frameworks › Language Servers (https://github.com/Hessesian/kmp-lsp)"
|
||||
Reference in new issue
Block a user