Skip to content

Instantly share code, notes, and snippets.

@stevoland
Created March 5, 2026 01:07
Show Gist options
  • Select an option

  • Save stevoland/f0f3c5ac12755e48db8459c8ff535cdd to your computer and use it in GitHub Desktop.

Select an option

Save stevoland/f0f3c5ac12755e48db8459c8ff535cdd to your computer and use it in GitHub Desktop.
opencode-jdtls-fix
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