Skip to content

feat(language_server): add support for intellij-lsp backend - #341

Draft
SparkyCloudy wants to merge 5 commits into
rcasia:feat/lsp-gateway-refactorfrom
SparkyCloudy:feat/intellij-language-server
Draft

SparkyCloudy wants to merge 5 commits into
rcasia:feat/lsp-gateway-refactorfrom
SparkyCloudy:feat/intellij-language-server

Conversation

@SparkyCloudy

@SparkyCloudy SparkyCloudy commented Oct 10, 2026 •

Copy link
Copy Markdown
Contributor

Transparency note: I'm a Windows user who wanted to run Java tests with neotest-java using IntelliJ LSP (intellij-lsp.nvim) instead of JDT.LS. I don't have deep knowledge of the full neotest-java codebase — I used an AI coding agent (Google Antigravity) to help implement and test this support on top of PR #335's JavaLanguageServer gateway seam. The changes work on my setup (Windows 11, Neovim 0.12 Nightly, Temurin JDK 21), and I've ensured all 209 unit tests pass deterministically. I'd appreciate maintainers reviewing whether the approach is sound. Happy to adjust anything based on feedback.

Base Branch Note

This PR builds directly on top of PR #335 (refactor(lsp): add JavaLanguageServer gateway seam). It implements the actual backend integration requested in #314 using the gateway architecture introduced in #335.


Problem

PR #335 introduces the JavaLanguageServer gateway abstraction, its resolvers were still exclusively written for Eclipse JDT.LS. Supporting IntelliJ LSP (intellij-lsp.nvim) required addressing several architectural differences between how JDT.LS and IntelliJ LSP operate:

1. Test scope resolution differences

Eclipse JDT.LS accepts workspace/project directories and an explicit parameter { scope = "test" }. In contrast, IntelliJ LSP (intellij.java.resolveLaunch and intellij.java.resolveBuildCommand) does not support a scope parameter — it infers execution scope purely from the document URI passed:

  • Passing a directory or project root causes IntelliJ LSP to resolve only production classes (target/classes) and production build commands (compile), missing test dependencies like JUnit/Mockito.
  • Passing a test document URI (src/test/**/*.java) triggers test scope, returning target/test-classes, test dependencies, and test-compile.

2. JDK binary vs JDK home directory

IntelliJ LSP's resolveLaunch returns res.javaExec pointing directly to the project's configured Java binary (<jdk>/bin/java or java.exe). However, downstream consumers in neotest-java (command/binaries.lua) expect the root JDK directory so they can locate both bin/java and bin/javap (used for method ID disambiguation).

3. External build command execution

Unlike JDT.LS which compiles internally within its language server process, IntelliJ LSP returns a CLI build command (e.g. mvnw test-compile or gradle testClasses). This needs to be spawned asynchronously via vim.system without blocking Neovim or violating fast-event / coroutine boundary rules.

4. Client detection restricted to JDT.LS

client_provider.lua was hardcoded to look only for jdtls, causing neotest-java to fail during test execution when running with IntelliJ LSP even if the LSP client was initialized.


Solution

This PR implements full, out-of-the-box support for IntelliJ LSP while preserving 100% backward compatibility for existing JDT.LS users.

1. Unified Gateway Dispatching (init.lua)

JavaLanguageServer symmetrically instantiates both JDTLS and IntelliJ resolvers. On each request, it inspects client_provider(cwd):

  • If client.name == "intellij", delegates to IntelliJ resolvers.
  • Otherwise (default), delegates to JDTLS resolvers. Upstream consumers (Binaries, ClasspathProvider, LspCompiler) require no changes.

2. Test-Scoped Document URI Resolution (document_uri.lua)

Added a helper that searches for a representative test file (src/test/**/*.java or *Test*.java) to supply to IntelliJ LSP commands.

  • Uses safe_glob prioritizing nio.fn.globpath over vim.fn.globpath to avoid E5560: textlock is active or fast-event context errors when called within async coroutines.

3. Dedicated IntelliJ Resolvers

  • intellij_java_home.lua: Extracts res.javaExec from intellij.java.resolveLaunch and calls .parent():parent() to resolve the JDK root folder for binaries.lua. Includes fallbacks to JAVA_HOME and vim.fn.exepath("java"), cached per directory.
  • intellij_classpath.lua: Retrieves classpath and modulePath (JPMS) in a single roundtrip from intellij.java.resolveLaunch and combines them with any additional classpath entries into a Classpath value object with platform path separators.
  • intellij_compile.lua: Requests intellij.java.resolveBuildCommand and executes the returned CLI command via vim.system. Safely awaits completion only when running inside an active coroutine (coroutine.running() ~= nil).

4. Multi-Client Detection in client_provider.lua

Updated client_provider.lua to check both "intellij" and "jdtls". Retains the maintainer's existing assertions and buffer-preloading polling logic.

5. Architecture Documentation (ARCHITECTURE.md)

Updated ARCHITECTURE.md to document the multi-LSP gateway, updating the dependency table, Flow 2 sequence diagram, and component layout.


Files Changed

File Change
lua/neotest-java/core/language_server/init.lua Added IntelliJ resolver wiring & dynamic backend dispatching
lua/neotest-java/core/language_server/document_uri.lua New helper to resolve test document URIs with async-safe globbing
lua/neotest-java/core/language_server/intellij_java_home.lua New resolver for JDK root derivation via javaExec
lua/neotest-java/core/language_server/intellij_classpath.lua New resolver combining classpath and modulePath from resolveLaunch
lua/neotest-java/core/language_server/intellij_compile.lua New compiler delegating to resolveBuildCommand via vim.system
lua/neotest-java/core/spec_builder/compiler/client_provider.lua Added support for detecting intellij client alongside jdtls
tests/unit/test_intellij_language_server_spec.lua 7 new unit tests covering all IntelliJ resolvers, fallbacks, and compiler states
tests/unit/test_client_provider_spec.lua Added unit test verifying intellij client detection
ARCHITECTURE.md Documented IntelliJ LSP support in flows and module breakdown

Testing

Tested manually on:

  • Windows 11 native: Neovim v0.12 Nightly, Temurin JDK 21, Maven multi-test project (belajar-java-resilience4j) with intellij-lsp.nvim.

Verifications:

  • nvim --headless -u scripts/minimal_init.lua -c "lua MiniTest.run()" runs and passes all 209 unit tests cleanly (0 failures, 0 errors) in ~0.15s.

  • 7 new unit tests in test_intellij_language_server_spec.lua comprehensively mock and test IntelliJ LSP request flows.

  • Verified test discovery, test execution, and result tree rendering inside live Neovim via <leader>tt.

  • Zero regressions on existing JDT.LS test cases.


Disclosure

Co-authored with AI coding assistant (Google Antigravity).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant