Created
March 5, 2026 01:07
-
-
Save stevoland/f0f3c5ac12755e48db8459c8ff535cdd to your computer and use it in GitHub Desktop.
opencode-jdtls-fix
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| import fs from 'node:fs/promises'; | |
| import os from 'node:os'; | |
| import path from 'node:path'; | |
| import type { Hooks, PluginInput } from '@opencode-ai/plugin'; | |
| import { $ } from 'bun'; | |
| import { JavaLspConfig } from '../config/schema'; | |
| import { log } from '../utils/logger'; | |
| export async function createJavaLspHook(options: JavaLspConfig, ctx: PluginInput) { | |
| if (!options?.enabled) { | |
| return {}; | |
| } | |
| const root = ctx.worktree || ctx.directory; | |
| const isGradleRepo = await fs | |
| .stat(path.join(root, 'build.gradle')) | |
| .then((s) => s.isFile()) | |
| .catch(() => false); | |
| if (!isGradleRepo) { | |
| return {}; | |
| } | |
| return { | |
| config: async (config) => { | |
| const command = await getCommand(); | |
| config.lsp = { | |
| ...config.lsp, | |
| jdtls: { | |
| disabled: true, | |
| }, | |
| }; | |
| if (!command) { | |
| log('Java LSP will be disabled due to missing requirements.'); | |
| return; | |
| } | |
| config.lsp['jdtls-fixed'] = { | |
| extensions: ['.java'], | |
| command | |
| }; | |
| }, | |
| } satisfies Hooks; | |
| } | |
| const homeDirectory = os.homedir(); | |
| const xdgDataBin = | |
| process.env['XDG_DATA_HOME'] || path.join(homeDirectory, '.local', 'share', 'bin'); | |
| const pathExists = async (p: string) => | |
| fs | |
| .stat(p) | |
| .then(() => true) | |
| .catch(() => false); | |
| function truthy(key: string) { | |
| const value = process.env[key]?.toLowerCase(); | |
| return value === 'true' || value === '1'; | |
| } | |
| const getCommand = async () => { | |
| const java = Bun.which('java'); | |
| if (!java) { | |
| log('Java 21 or newer is required to run the JDTLS. Please install it first.'); | |
| return; | |
| } | |
| const javaMajorVersion = await $`java -version` | |
| .quiet() | |
| .nothrow() | |
| .then(({ stderr }) => { | |
| const m = /"(\d+)\.\d+\.\d+"/.exec(stderr.toString()); | |
| return !m || !m[1] ? undefined : parseInt(m[1]); | |
| }); | |
| if (javaMajorVersion == null || javaMajorVersion < 21) { | |
| log('JDTLS requires at least Java 21.'); | |
| return; | |
| } | |
| const distPath = path.join(xdgDataBin, 'jdtls'); | |
| const launcherDir = path.join(distPath, 'plugins'); | |
| const installed = await pathExists(launcherDir); | |
| if (!installed) { | |
| if (truthy(process.env['OPENCODE_DISABLE_LSP_DOWNLOAD'] || '0')) return; | |
| log('Downloading JDTLS LSP server.'); | |
| await fs.mkdir(distPath, { recursive: true }); | |
| const releaseURL = | |
| 'https://www.eclipse.org/downloads/download.php?file=/jdtls/snapshots/jdt-language-server-latest.tar.gz'; | |
| const archiveName = 'release.tar.gz'; | |
| log('Downloading JDTLS archive', { url: releaseURL, dest: distPath }); | |
| const curlResult = await $`curl -L -o ${archiveName} '${releaseURL}'` | |
| .cwd(distPath) | |
| .quiet() | |
| .nothrow(); | |
| if (curlResult.exitCode !== 0) { | |
| log('Failed to download JDTLS', { | |
| exitCode: curlResult.exitCode, | |
| stderr: curlResult.stderr.toString(), | |
| }); | |
| return; | |
| } | |
| log('Extracting JDTLS archive'); | |
| const tarResult = await $`tar -xzf ${archiveName}`.cwd(distPath).quiet().nothrow(); | |
| if (tarResult.exitCode !== 0) { | |
| log('Failed to extract JDTLS', { | |
| exitCode: tarResult.exitCode, | |
| stderr: tarResult.stderr.toString(), | |
| }); | |
| return; | |
| } | |
| await fs.rm(path.join(distPath, archiveName), { force: true }); | |
| log('JDTLS download and extraction completed'); | |
| } | |
| const jarFileName = await $`ls org.eclipse.equinox.launcher_*.jar` | |
| .cwd(launcherDir) | |
| .quiet() | |
| .nothrow() | |
| .then(({ stdout }) => stdout.toString().trim()); | |
| const launcherJar = path.join(launcherDir, jarFileName); | |
| if (!(await pathExists(launcherJar))) { | |
| log(`Failed to locate the JDTLS launcher module in the installed directory: ${distPath}.`); | |
| return; | |
| } | |
| const configFile = path.join( | |
| distPath, | |
| (() => { | |
| switch (process.platform) { | |
| case 'darwin': | |
| return 'config_mac'; | |
| case 'linux': | |
| return 'config_linux'; | |
| case 'win32': | |
| return 'config_win'; | |
| default: | |
| return 'config_linux'; | |
| } | |
| })(), | |
| ); | |
| const dataDir = await fs.mkdtemp(path.join(os.tmpdir(), 'opencode-jdtls-data')); | |
| return [ | |
| java, | |
| '-jar', | |
| launcherJar, | |
| '-configuration', | |
| configFile, | |
| '-data', | |
| dataDir, | |
| '-Declipse.application=org.eclipse.jdt.ls.core.id1', | |
| '-Dosgi.bundles.defaultStartLevel=4', | |
| '-Declipse.product=org.eclipse.jdt.ls.core.product', | |
| '-Dlog.level=ALL', | |
| '--add-modules=ALL-SYSTEM', | |
| '--add-opens java.base/java.util=ALL-UNNAMED', | |
| '--add-opens java.base/java.lang=ALL-UNNAMED', | |
| ]; | |
| }; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment