@vscjava/java-language-server 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -0
- package/bin/jdtls.js +282 -0
- package/lib/detect.js +121 -0
- package/lib/install.js +160 -0
- package/lib/postinstall.js +140 -0
- package/package.json +39 -0
- package/server/features/org.eclipse.equinox.executable_3.8.3200.v20260203-2149.jar +0 -0
- package/server/plugins/ch.qos.logback.classic_1.5.21.jar +0 -0
- package/server/plugins/ch.qos.logback.core_1.5.21.jar +0 -0
- package/server/plugins/com.google.gson_2.13.2.jar +0 -0
- package/server/plugins/com.google.guava.failureaccess_1.0.3.jar +0 -0
- package/server/plugins/com.google.guava_33.5.0.jre.jar +0 -0
- package/server/plugins/com.sun.jna.platform_5.18.1.jar +0 -0
- package/server/plugins/com.sun.jna_5.18.1.v20251001-0800.jar +0 -0
- package/server/plugins/jakarta.annotation-api_1.3.5.jar +0 -0
- package/server/plugins/jakarta.inject.jakarta.inject-api_1.0.5.jar +0 -0
- package/server/plugins/jakarta.servlet-api_6.1.0.jar +0 -0
- package/server/plugins/org.apache.ant_1.10.15.v20240901-1000.jar +0 -0
- package/server/plugins/org.apache.aries.spifly.dynamic.bundle_1.3.7.jar +0 -0
- package/server/plugins/org.apache.commons.cli_1.11.0.jar +0 -0
- package/server/plugins/org.apache.commons.commons-codec_1.20.0.jar +0 -0
- package/server/plugins/org.apache.commons.lang3_3.20.0.jar +0 -0
- package/server/plugins/org.apache.felix.scr_2.2.14.jar +0 -0
- package/server/plugins/org.commonmark.ext-gfm-tables_0.27.1.jar +0 -0
- package/server/plugins/org.commonmark_0.27.1.jar +0 -0
- package/server/plugins/org.eclipse.ant.core_3.7.800.v20260130-1053.jar +0 -0
- package/server/plugins/org.eclipse.buildship.compat_3.1.10.v20250827-0209-s.jar +0 -0
- package/server/plugins/org.eclipse.buildship.core_3.1.10.v20250827-0209-s.jar +0 -0
- package/server/plugins/org.eclipse.compare.core_3.8.800.v20250718-1505.jar +0 -0
- package/server/plugins/org.eclipse.core.commands_3.12.500.v20251103-0733.jar +0 -0
- package/server/plugins/org.eclipse.core.contenttype_3.9.800.v20251105-1620.jar +0 -0
- package/server/plugins/org.eclipse.core.expressions_3.9.500.v20250608-0434.jar +0 -0
- package/server/plugins/org.eclipse.core.filebuffers_3.8.500.v20251103-0746.jar +0 -0
- package/server/plugins/org.eclipse.core.filesystem_1.11.400.v20251107-0507.jar +0 -0
- package/server/plugins/org.eclipse.core.jobs_3.15.700.v20250725-1147.jar +0 -0
- package/server/plugins/org.eclipse.core.net_1.5.800.v20250613-1119.jar +0 -0
- package/server/plugins/org.eclipse.core.resources_3.23.200.v20251217-0810.jar +0 -0
- package/server/plugins/org.eclipse.core.runtime_3.34.200.v20251220-0953.jar +0 -0
- package/server/plugins/org.eclipse.core.variables_3.6.700.v20250913-1442.jar +0 -0
- package/server/plugins/org.eclipse.debug.core_3.23.200.v20251107-0507.jar +0 -0
- package/server/plugins/org.eclipse.equinox.app_1.7.600.v20251211-1038.jar +0 -0
- package/server/plugins/org.eclipse.equinox.common_3.20.300.v20251111-0312.jar +0 -0
- package/server/plugins/org.eclipse.equinox.frameworkadmin.equinox_1.3.400.v20250515-0513.jar +0 -0
- package/server/plugins/org.eclipse.equinox.frameworkadmin_2.3.500.v20250716-0529.jar +0 -0
- package/server/plugins/org.eclipse.equinox.http.service.api_1.2.102.v20250520-0629.jar +0 -0
- package/server/plugins/org.eclipse.equinox.launcher.cocoa.macosx.aarch64_1.2.1400.v20250801-0854.jar +0 -0
- package/server/plugins/org.eclipse.equinox.launcher.cocoa.macosx.x86_64_1.2.1400.v20250801-0854.jar +0 -0
- package/server/plugins/org.eclipse.equinox.launcher.gtk.linux.aarch64_1.2.1500.v20250801-0854.jar +0 -0
- package/server/plugins/org.eclipse.equinox.launcher.gtk.linux.x86_64_1.2.1500.v20250801-0854.jar +0 -0
- package/server/plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.3.0.v20260203-2149.jar +0 -0
- package/server/plugins/org.eclipse.equinox.launcher_1.7.100.v20251111-0406.jar +0 -0
- package/server/plugins/org.eclipse.equinox.preferences_3.12.100.v20251111-0704.jar +0 -0
- package/server/plugins/org.eclipse.equinox.registry_3.12.600.v20250906-0651.jar +0 -0
- package/server/plugins/org.eclipse.equinox.security.linux_1.1.400.v20250521-0415.jar +0 -0
- package/server/plugins/org.eclipse.equinox.security.macosx_1.102.500.v20250521-0414.jar +0 -0
- package/server/plugins/org.eclipse.equinox.security.win32_1.3.0.v20240419-2334.jar +0 -0
- package/server/plugins/org.eclipse.equinox.security_1.4.700.v20250622-1644.jar +0 -0
- package/server/plugins/org.eclipse.equinox.simpleconfigurator.manipulator_2.3.600.v20250729-0655.jar +0 -0
- package/server/plugins/org.eclipse.equinox.simpleconfigurator_1.5.700.v20251111-1031.jar +0 -0
- package/server/plugins/org.eclipse.jdt.apt.core_3.8.600.v20241001-0914.jar +0 -0
- package/server/plugins/org.eclipse.jdt.apt.pluggable.core_1.4.700.v20251202-0459.jar +0 -0
- package/server/plugins/org.eclipse.jdt.core.compiler.batch_3.45.0.v20260224-0835.jar +0 -0
- package/server/plugins/org.eclipse.jdt.core.javac_1.0.0.z20260225-2209.jar +0 -0
- package/server/plugins/org.eclipse.jdt.core.manipulation_1.24.0.v20260212-2209.jar +0 -0
- package/server/plugins/org.eclipse.jdt.core_3.45.0.v20260219-1233.jar +0 -0
- package/server/plugins/org.eclipse.jdt.debug_3.25.100.v20260212-0641.jar +0 -0
- package/server/plugins/org.eclipse.jdt.junit.core_3.14.100.v20251223-2158.jar +0 -0
- package/server/plugins/org.eclipse.jdt.junit.runtime_3.8.0.v20251113-1434.jar +0 -0
- package/server/plugins/org.eclipse.jdt.launching.macosx_3.6.400.v20260211-1052.jar +0 -0
- package/server/plugins/org.eclipse.jdt.launching_3.24.100.v20260218-1303.jar +0 -0
- package/server/plugins/org.eclipse.jdt.ls.core_1.57.0.202602261110.jar +0 -0
- package/server/plugins/org.eclipse.jdt.ls.filesystem_1.57.0.202602261110.jar +0 -0
- package/server/plugins/org.eclipse.jdt.ls.logback.appender_1.57.0.202602261110.jar +0 -0
- package/server/plugins/org.eclipse.jetty.servlet-api_4.0.9.jar +0 -0
- package/server/plugins/org.eclipse.lsp4j.jsonrpc_0.24.0.v20250131-1745.jar +0 -0
- package/server/plugins/org.eclipse.lsp4j_0.24.0.v20250131-1745.jar +0 -0
- package/server/plugins/org.eclipse.ltk.core.refactoring_3.15.100.v20251023-1358.jar +0 -0
- package/server/plugins/org.eclipse.m2e.apt.core_2.3.100.20250418-1315.jar +0 -0
- package/server/plugins/org.eclipse.m2e.core_2.7.600.20251121-1832.jar +0 -0
- package/server/plugins/org.eclipse.m2e.jdt_2.5.0.20251112-1507.jar +0 -0
- package/server/plugins/org.eclipse.m2e.maven.runtime_3.9.1101.20251020-1549.jar +0 -0
- package/server/plugins/org.eclipse.m2e.workspace.cli_0.4.0.jar +0 -0
- package/server/plugins/org.eclipse.osgi.compatibility.state_1.3.0.v20251022-1724.jar +0 -0
- package/server/plugins/org.eclipse.osgi.services_3.12.300.v20250707-1221.jar +0 -0
- package/server/plugins/org.eclipse.osgi_3.24.100.v20251215-1416.jar +0 -0
- package/server/plugins/org.eclipse.search.core_3.16.600.v20250920-0652.jar +0 -0
- package/server/plugins/org.eclipse.text_3.14.600.v20260112-1806.jar +0 -0
- package/server/plugins/org.eclipse.xtext.xbase.lib_2.41.0.v20251124-0739.jar +0 -0
- package/server/plugins/org.gradle.toolingapi_8.9.0.v20250827-0209-s.jar +0 -0
- package/server/plugins/org.hamcrest_3.0.0.jar +0 -0
- package/server/plugins/org.jsoup_1.19.1.jar +0 -0
- package/server/plugins/org.junit_4.13.2.v20240929-1000.jar +0 -0
- package/server/plugins/org.objectweb.asm.commons_9.9.1.jar +0 -0
- package/server/plugins/org.objectweb.asm.tree.analysis_9.9.1.jar +0 -0
- package/server/plugins/org.objectweb.asm.tree_9.9.1.jar +0 -0
- package/server/plugins/org.objectweb.asm.util_9.9.1.jar +0 -0
- package/server/plugins/org.objectweb.asm_9.9.1.jar +0 -0
- package/server/plugins/org.osgi.service.cm_1.6.1.202109301733.jar +0 -0
- package/server/plugins/org.osgi.service.component_1.5.1.202212101352.jar +0 -0
- package/server/plugins/org.osgi.service.device_1.1.1.202109301733.jar +0 -0
- package/server/plugins/org.osgi.service.event_1.4.1.202109301733.jar +0 -0
- package/server/plugins/org.osgi.service.http.whiteboard_1.1.1.202109301733.jar +0 -0
- package/server/plugins/org.osgi.service.metatype_1.4.1.202109301733.jar +0 -0
- package/server/plugins/org.osgi.service.prefs_1.1.2.202109301733.jar +0 -0
- package/server/plugins/org.osgi.service.provisioning_1.2.0.201505202024.jar +0 -0
- package/server/plugins/org.osgi.service.upnp_1.2.1.202109301733.jar +0 -0
- package/server/plugins/org.osgi.service.useradmin_1.1.1.202109301733.jar +0 -0
- package/server/plugins/org.osgi.service.wireadmin_1.0.2.202109301733.jar +0 -0
- package/server/plugins/org.osgi.util.function_1.2.0.202109301733.jar +0 -0
- package/server/plugins/org.osgi.util.promise_1.3.0.202212101352.jar +0 -0
- package/server/plugins/slf4j.api_2.0.17.jar +0 -0
- package/server/plugins/wrapped.com.jetbrains.intellij.java.java-decompiler-engine_253.29346.240.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-ext-emoji_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-ext-gfm-strikethrough_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-ext-ins_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-ext-superscript_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-ext-tables_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-ext-wikilink_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-html2md-converter_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-jira-converter_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-ast_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-builder_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-collection_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-data_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-dependency_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-format_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-html_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-misc_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-options_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-sequence_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-visitor_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util_0.64.8.jar +0 -0
- package/server/plugins/wrapped.com.vladsch.flexmark.flexmark_0.64.8.jar +0 -0
- package/server/plugins/wrapped.org.jetbrains.annotations_24.0.1.jar +0 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @vscjava/java-language-server
|
|
2
|
+
|
|
3
|
+
Java Language Server (Eclipse JDT LS wrapper) distributed via npm, designed for integration with GitHub Copilot CLI.
|
|
4
|
+
|
|
5
|
+
## Overview
|
|
6
|
+
|
|
7
|
+
This package wraps [Eclipse JDT Language Server](https://github.com/eclipse-jdtls/eclipse.jdt.ls) and distributes it via npm following the same platform-specific pattern used by `@vscjava/cpp-language-server`.
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
npm install -g @vscjava/java-language-server
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
The installation automatically downloads the correct platform configuration for your OS.
|
|
16
|
+
To bundle a JRE (no separate Java install needed):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install -g @vscjava/java-language-server @vscjava/java-ls-jre-win32-x64
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
- **Node.js** >= 16
|
|
25
|
+
- **Java 21+** (unless using the bundled JRE package)
|
|
26
|
+
|
|
27
|
+
## Usage
|
|
28
|
+
|
|
29
|
+
### Standalone
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
jdtls --stdio
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
### With Copilot CLI
|
|
36
|
+
|
|
37
|
+
Add to `~/.copilot/lsp-config.json`:
|
|
38
|
+
|
|
39
|
+
```json
|
|
40
|
+
{
|
|
41
|
+
"lspServers": {
|
|
42
|
+
"java": {
|
|
43
|
+
"command": "jdtls",
|
|
44
|
+
"args": ["--stdio"],
|
|
45
|
+
"fileExtensions": {
|
|
46
|
+
".java": "java"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### Project Configuration
|
|
54
|
+
|
|
55
|
+
Create `java-lsp.json` in your project root:
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"repositoryPath": "/path/to/project",
|
|
60
|
+
"java": {
|
|
61
|
+
"home": null,
|
|
62
|
+
"vmargs": ["-Xmx2G"]
|
|
63
|
+
},
|
|
64
|
+
"project": {
|
|
65
|
+
"type": "auto",
|
|
66
|
+
"importOnStartup": true
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
## Java Runtime Resolution Order
|
|
72
|
+
|
|
73
|
+
1. Bundled JRE (optional `@vscjava/java-ls-jre-*` package)
|
|
74
|
+
2. `java.home` in `java-lsp.json`
|
|
75
|
+
3. `JAVA_HOME` environment variable
|
|
76
|
+
4. `java` on system PATH
|
|
77
|
+
|
|
78
|
+
## Architecture
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
@vscjava/java-language-server (main package - Node.js shell + shared JARs)
|
|
82
|
+
├── bin/jdtls.js (entry point)
|
|
83
|
+
├── lib/install.js (platform detection, JVM resolution)
|
|
84
|
+
├── lib/detect.js (Maven/Gradle project detection)
|
|
85
|
+
├── server/plugins/ (Eclipse JDT LS jars - cross-platform)
|
|
86
|
+
└── optionalDependencies:
|
|
87
|
+
├── @vscjava/java-ls-config-* (platform-specific launcher config)
|
|
88
|
+
└── @vscjava/java-ls-jre-* (optional bundled JRE)
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
## Server Files
|
|
92
|
+
|
|
93
|
+
After installing, place the jdtls server files in `server/`:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# Download jdtls
|
|
97
|
+
curl -L https://download.eclipse.org/jdtls/milestones/1.40.0/jdt-language-server-1.40.0-202410311350.tar.gz | tar xz -C node_modules/@vscjava/java-language-server/server/
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
## License
|
|
101
|
+
|
|
102
|
+
EPL-2.0 (Eclipse JDT LS), see individual packages for details.
|
package/bin/jdtls.js
ADDED
|
@@ -0,0 +1,282 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
'use strict';
|
|
4
|
+
|
|
5
|
+
const { spawn } = require('child_process');
|
|
6
|
+
const path = require('path');
|
|
7
|
+
const fs = require('fs');
|
|
8
|
+
const { findJava, findConfigDir, findLauncherJar, getWorkspaceDir } = require('../lib/install');
|
|
9
|
+
const { detectProject } = require('../lib/detect');
|
|
10
|
+
|
|
11
|
+
const SERVER_DIR = path.join(__dirname, '..', 'server');
|
|
12
|
+
|
|
13
|
+
function loadConfig() {
|
|
14
|
+
let dir = process.cwd();
|
|
15
|
+
while (dir) {
|
|
16
|
+
const configPath = path.join(dir, 'java-lsp.json');
|
|
17
|
+
if (fs.existsSync(configPath)) {
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
20
|
+
} catch (e) {
|
|
21
|
+
console.error(`Warning: Failed to parse ${configPath}: ${e.message}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const parent = path.dirname(dir);
|
|
25
|
+
if (parent === dir) break;
|
|
26
|
+
dir = parent;
|
|
27
|
+
}
|
|
28
|
+
return {};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function buildArgs(config) {
|
|
32
|
+
const javaExe = findJava(config);
|
|
33
|
+
if (!javaExe) {
|
|
34
|
+
console.error('ERROR: Java 21+ runtime not found.');
|
|
35
|
+
console.error('Install Java 21+ and set JAVA_HOME, or install the optional JRE package:');
|
|
36
|
+
console.error(` npm install @vscjava/java-ls-jre-${process.platform}-${process.arch}`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const configDir = findConfigDir();
|
|
41
|
+
if (!configDir) {
|
|
42
|
+
console.error(`ERROR: No platform configuration found for ${process.platform}.`);
|
|
43
|
+
console.error('Supported platforms: win32, linux, darwin');
|
|
44
|
+
process.exit(1);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const launcher = findLauncherJar(SERVER_DIR);
|
|
48
|
+
if (!launcher) {
|
|
49
|
+
console.error('ERROR: Eclipse Equinox launcher jar not found in server/plugins/.');
|
|
50
|
+
console.error('The package may be corrupted. Try reinstalling.');
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const workspaceDir = getWorkspaceDir(config);
|
|
55
|
+
const vmargs = (config.java && config.java.vmargs) || ['-Xmx1G'];
|
|
56
|
+
|
|
57
|
+
const args = [
|
|
58
|
+
...vmargs,
|
|
59
|
+
'-Declipse.application=org.eclipse.jdt.ls.core.id1',
|
|
60
|
+
'-Dosgi.bundles.defaultStartLevel=4',
|
|
61
|
+
'-Dosgi.checkConfiguration=true',
|
|
62
|
+
'-Declipse.product=org.eclipse.jdt.ls.core.product',
|
|
63
|
+
'-Dlog.level=ALL',
|
|
64
|
+
'--add-modules=ALL-SYSTEM',
|
|
65
|
+
'--add-opens', 'java.base/java.util=ALL-UNNAMED',
|
|
66
|
+
'--add-opens', 'java.base/java.lang=ALL-UNNAMED',
|
|
67
|
+
'-jar', launcher,
|
|
68
|
+
'-configuration', configDir,
|
|
69
|
+
'-data', workspaceDir,
|
|
70
|
+
];
|
|
71
|
+
|
|
72
|
+
args.push(...process.argv.slice(2));
|
|
73
|
+
|
|
74
|
+
return { javaExe, args };
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Encode a JSON-RPC message with Content-Length header for LSP protocol.
|
|
79
|
+
*/
|
|
80
|
+
function encodeLspMessage(msg) {
|
|
81
|
+
const json = JSON.stringify(msg);
|
|
82
|
+
const byteLength = Buffer.byteLength(json, 'utf8');
|
|
83
|
+
return `Content-Length: ${byteLength}\r\n\r\n${json}`;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Fix malformed file:// URIs.
|
|
88
|
+
* Copilot CLI sends "file://C:\path" but LSP spec requires "file:///C:/path".
|
|
89
|
+
*/
|
|
90
|
+
function fixFileUri(uri) {
|
|
91
|
+
if (!uri || !uri.startsWith('file://')) return uri;
|
|
92
|
+
// Remove the file:// prefix
|
|
93
|
+
let path = uri.substring(7);
|
|
94
|
+
// Replace backslashes with forward slashes
|
|
95
|
+
path = path.replace(/\\/g, '/');
|
|
96
|
+
// Ensure leading slash for absolute paths (e.g., "C:/..." → "/C:/...")
|
|
97
|
+
if (path.match(/^[A-Za-z]:/)) {
|
|
98
|
+
path = '/' + path;
|
|
99
|
+
}
|
|
100
|
+
// Ensure it doesn't already have the triple slash
|
|
101
|
+
if (!path.startsWith('/')) {
|
|
102
|
+
path = '/' + path;
|
|
103
|
+
}
|
|
104
|
+
return 'file://' + path;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Fix rootUri and workspaceFolders in an initialize request message.
|
|
109
|
+
*/
|
|
110
|
+
function fixInitializeRequest(text) {
|
|
111
|
+
const headerEnd = text.indexOf('\r\n\r\n');
|
|
112
|
+
if (headerEnd === -1) return text;
|
|
113
|
+
|
|
114
|
+
const header = text.substring(0, headerEnd);
|
|
115
|
+
const body = text.substring(headerEnd + 4);
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const msg = JSON.parse(body);
|
|
119
|
+
if (!msg.params) return text;
|
|
120
|
+
|
|
121
|
+
let modified = false;
|
|
122
|
+
|
|
123
|
+
if (msg.params.rootUri) {
|
|
124
|
+
const fixed = fixFileUri(msg.params.rootUri);
|
|
125
|
+
if (fixed !== msg.params.rootUri) {
|
|
126
|
+
console.error(`[jdtls-proxy] Fixed rootUri: ${msg.params.rootUri} → ${fixed}`);
|
|
127
|
+
msg.params.rootUri = fixed;
|
|
128
|
+
modified = true;
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
if (msg.params.rootPath) {
|
|
133
|
+
msg.params.rootPath = msg.params.rootPath.replace(/\\/g, '/');
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (Array.isArray(msg.params.workspaceFolders)) {
|
|
137
|
+
for (const folder of msg.params.workspaceFolders) {
|
|
138
|
+
if (folder.uri) {
|
|
139
|
+
const fixed = fixFileUri(folder.uri);
|
|
140
|
+
if (fixed !== folder.uri) {
|
|
141
|
+
console.error(`[jdtls-proxy] Fixed workspace folder URI: ${folder.uri} → ${fixed}`);
|
|
142
|
+
folder.uri = fixed;
|
|
143
|
+
modified = true;
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
if (modified) {
|
|
150
|
+
return encodeLspMessage(msg);
|
|
151
|
+
}
|
|
152
|
+
} catch {
|
|
153
|
+
// Not valid JSON, return as-is
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
return text;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/**
|
|
160
|
+
* Lightweight LSP proxy that:
|
|
161
|
+
* 1. Fixes malformed file:// URIs in initialize requests
|
|
162
|
+
* 2. Automatically sends 'initialized' notification to jdtls
|
|
163
|
+
*/
|
|
164
|
+
function createLspProxy(child) {
|
|
165
|
+
let sentInitialized = false;
|
|
166
|
+
let clientBuffer = '';
|
|
167
|
+
let buffering = true;
|
|
168
|
+
|
|
169
|
+
// Client → Server: intercept initialize request to fix URIs
|
|
170
|
+
process.stdin.on('data', (chunk) => {
|
|
171
|
+
if (!buffering) {
|
|
172
|
+
// After init handshake, direct passthrough
|
|
173
|
+
child.stdin.write(chunk);
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
clientBuffer += chunk.toString('utf8');
|
|
178
|
+
|
|
179
|
+
// Check if we have a complete initialize request
|
|
180
|
+
if (clientBuffer.includes('"method":"initialize"') || clientBuffer.includes('"method": "initialize"')) {
|
|
181
|
+
const headerEnd = clientBuffer.indexOf('\r\n\r\n');
|
|
182
|
+
if (headerEnd !== -1) {
|
|
183
|
+
const header = clientBuffer.substring(0, headerEnd);
|
|
184
|
+
const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
|
|
185
|
+
if (lengthMatch) {
|
|
186
|
+
const contentLength = parseInt(lengthMatch[1], 10);
|
|
187
|
+
const bodyStart = headerEnd + 4;
|
|
188
|
+
if (clientBuffer.length >= bodyStart + contentLength) {
|
|
189
|
+
// We have the complete initialize message
|
|
190
|
+
const fullMessage = clientBuffer.substring(0, bodyStart + contentLength);
|
|
191
|
+
const remaining = clientBuffer.substring(bodyStart + contentLength);
|
|
192
|
+
|
|
193
|
+
// Fix URIs and forward
|
|
194
|
+
const fixed = fixInitializeRequest(fullMessage);
|
|
195
|
+
child.stdin.write(fixed);
|
|
196
|
+
|
|
197
|
+
// Forward any remaining data
|
|
198
|
+
if (remaining.length > 0) {
|
|
199
|
+
child.stdin.write(remaining);
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
buffering = false;
|
|
203
|
+
sentInitialized = true;
|
|
204
|
+
clientBuffer = '';
|
|
205
|
+
|
|
206
|
+
// Send initialized notification after server processes initialize
|
|
207
|
+
setTimeout(() => {
|
|
208
|
+
console.error('[jdtls-proxy] Sending initialized notification to server');
|
|
209
|
+
const initialized = encodeLspMessage({ jsonrpc: '2.0', method: 'initialized', params: {} });
|
|
210
|
+
child.stdin.write(initialized);
|
|
211
|
+
}, 3000);
|
|
212
|
+
|
|
213
|
+
// Switch to direct piping
|
|
214
|
+
process.stdin.removeAllListeners('data');
|
|
215
|
+
process.stdin.pipe(child.stdin);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// If buffer gets too big without finding initialize, flush and passthrough
|
|
223
|
+
if (clientBuffer.length > 50000) {
|
|
224
|
+
child.stdin.write(clientBuffer);
|
|
225
|
+
clientBuffer = '';
|
|
226
|
+
buffering = false;
|
|
227
|
+
process.stdin.removeAllListeners('data');
|
|
228
|
+
process.stdin.pipe(child.stdin);
|
|
229
|
+
}
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Server → Client: direct passthrough (no interception needed)
|
|
233
|
+
child.stdout.pipe(process.stdout);
|
|
234
|
+
|
|
235
|
+
// Forward server stderr to our stderr
|
|
236
|
+
child.stderr.pipe(process.stderr);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
function main() {
|
|
240
|
+
const config = loadConfig();
|
|
241
|
+
|
|
242
|
+
const projectInfo = detectProject(config.repositoryPath || process.cwd());
|
|
243
|
+
if (projectInfo.type !== 'unknown') {
|
|
244
|
+
console.error(`Detected ${projectInfo.type} project: ${projectInfo.buildFile}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const { javaExe, args } = buildArgs(config);
|
|
248
|
+
|
|
249
|
+
console.error(`Starting jdtls with: ${javaExe}`);
|
|
250
|
+
|
|
251
|
+
const child = spawn(javaExe, args, {
|
|
252
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
253
|
+
windowsHide: false,
|
|
254
|
+
env: { ...process.env },
|
|
255
|
+
});
|
|
256
|
+
|
|
257
|
+
// Set up the LSP proxy to inject 'initialized' notification
|
|
258
|
+
createLspProxy(child);
|
|
259
|
+
|
|
260
|
+
child.on('exit', (code, signal) => {
|
|
261
|
+
if (signal) {
|
|
262
|
+
process.kill(process.pid, signal);
|
|
263
|
+
} else {
|
|
264
|
+
process.exit(code || 0);
|
|
265
|
+
}
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
child.on('error', (err) => {
|
|
269
|
+
console.error(`Failed to start jdtls: ${err.message}`);
|
|
270
|
+
if (err.code === 'ENOENT') {
|
|
271
|
+
console.error(`Java executable not found at: ${javaExe}`);
|
|
272
|
+
console.error('Ensure Java 21+ is installed and JAVA_HOME is set correctly.');
|
|
273
|
+
}
|
|
274
|
+
process.exit(1);
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
// Handle parent process signals
|
|
278
|
+
process.on('SIGTERM', () => child.kill('SIGTERM'));
|
|
279
|
+
process.on('SIGINT', () => child.kill('SIGINT'));
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
main();
|
package/lib/detect.js
ADDED
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
const PROJECT_TYPES = {
|
|
7
|
+
MAVEN: 'maven',
|
|
8
|
+
GRADLE: 'gradle',
|
|
9
|
+
UNKNOWN: 'unknown',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const BUILD_FILES = {
|
|
13
|
+
[PROJECT_TYPES.MAVEN]: ['pom.xml'],
|
|
14
|
+
[PROJECT_TYPES.GRADLE]: ['build.gradle', 'build.gradle.kts', 'settings.gradle', 'settings.gradle.kts'],
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Detect the Java project type by looking for build files.
|
|
19
|
+
* Searches from the given directory upward to the filesystem root.
|
|
20
|
+
*/
|
|
21
|
+
function detectProject(startDir) {
|
|
22
|
+
let dir = startDir || process.cwd();
|
|
23
|
+
|
|
24
|
+
while (dir) {
|
|
25
|
+
// Check Maven
|
|
26
|
+
for (const buildFile of BUILD_FILES[PROJECT_TYPES.MAVEN]) {
|
|
27
|
+
const fullPath = path.join(dir, buildFile);
|
|
28
|
+
if (fs.existsSync(fullPath)) {
|
|
29
|
+
return {
|
|
30
|
+
type: PROJECT_TYPES.MAVEN,
|
|
31
|
+
buildFile: fullPath,
|
|
32
|
+
projectRoot: dir,
|
|
33
|
+
settings: detectMavenSettings(dir),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Check Gradle
|
|
39
|
+
for (const buildFile of BUILD_FILES[PROJECT_TYPES.GRADLE]) {
|
|
40
|
+
const fullPath = path.join(dir, buildFile);
|
|
41
|
+
if (fs.existsSync(fullPath)) {
|
|
42
|
+
return {
|
|
43
|
+
type: PROJECT_TYPES.GRADLE,
|
|
44
|
+
buildFile: fullPath,
|
|
45
|
+
projectRoot: dir,
|
|
46
|
+
settings: detectGradleSettings(dir),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const parent = path.dirname(dir);
|
|
52
|
+
if (parent === dir) break;
|
|
53
|
+
dir = parent;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return {
|
|
57
|
+
type: PROJECT_TYPES.UNKNOWN,
|
|
58
|
+
buildFile: null,
|
|
59
|
+
projectRoot: startDir,
|
|
60
|
+
settings: {},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Detect Maven-specific settings.
|
|
66
|
+
*/
|
|
67
|
+
function detectMavenSettings(projectRoot) {
|
|
68
|
+
const settings = {
|
|
69
|
+
hasWrapper: false,
|
|
70
|
+
isMultiModule: false,
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Check for Maven wrapper
|
|
74
|
+
const wrapperScript = process.platform === 'win32' ? 'mvnw.cmd' : 'mvnw';
|
|
75
|
+
settings.hasWrapper = fs.existsSync(path.join(projectRoot, wrapperScript));
|
|
76
|
+
|
|
77
|
+
// Check for multi-module (look for <modules> in pom.xml)
|
|
78
|
+
try {
|
|
79
|
+
const pomContent = fs.readFileSync(path.join(projectRoot, 'pom.xml'), 'utf8');
|
|
80
|
+
settings.isMultiModule = /<modules>/.test(pomContent);
|
|
81
|
+
} catch {
|
|
82
|
+
// Ignore read errors
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
return settings;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
/**
|
|
89
|
+
* Detect Gradle-specific settings.
|
|
90
|
+
*/
|
|
91
|
+
function detectGradleSettings(projectRoot) {
|
|
92
|
+
const settings = {
|
|
93
|
+
hasWrapper: false,
|
|
94
|
+
isKotlinDsl: false,
|
|
95
|
+
isMultiProject: false,
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// Check for Gradle wrapper
|
|
99
|
+
const wrapperScript = process.platform === 'win32' ? 'gradlew.bat' : 'gradlew';
|
|
100
|
+
settings.hasWrapper = fs.existsSync(path.join(projectRoot, wrapperScript));
|
|
101
|
+
|
|
102
|
+
// Check for Kotlin DSL
|
|
103
|
+
settings.isKotlinDsl = fs.existsSync(path.join(projectRoot, 'build.gradle.kts'))
|
|
104
|
+
|| fs.existsSync(path.join(projectRoot, 'settings.gradle.kts'));
|
|
105
|
+
|
|
106
|
+
// Check for multi-project (settings.gradle exists with include)
|
|
107
|
+
const settingsFile = settings.isKotlinDsl ? 'settings.gradle.kts' : 'settings.gradle';
|
|
108
|
+
try {
|
|
109
|
+
const content = fs.readFileSync(path.join(projectRoot, settingsFile), 'utf8');
|
|
110
|
+
settings.isMultiProject = /include\s*[("']/.test(content);
|
|
111
|
+
} catch {
|
|
112
|
+
// Ignore read errors
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
return settings;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
module.exports = {
|
|
119
|
+
detectProject,
|
|
120
|
+
PROJECT_TYPES,
|
|
121
|
+
};
|
package/lib/install.js
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
|
|
6
|
+
const PLATFORM_CONFIG_MAP = {
|
|
7
|
+
win32: 'config_win',
|
|
8
|
+
linux: 'config_linux',
|
|
9
|
+
darwin: 'config_mac',
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
const PLATFORM_PKG_MAP = {
|
|
13
|
+
win32: '@vscjava/java-ls-config-win32',
|
|
14
|
+
linux: '@vscjava/java-ls-config-linux',
|
|
15
|
+
darwin: '@vscjava/java-ls-config-darwin',
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Find a Java executable. Priority:
|
|
20
|
+
* 1. Bundled JRE (optional npm package)
|
|
21
|
+
* 2. Config-specified java.home
|
|
22
|
+
* 3. JAVA_HOME environment variable
|
|
23
|
+
* 4. 'java' on PATH
|
|
24
|
+
*/
|
|
25
|
+
function findJava(config) {
|
|
26
|
+
const javaExeName = process.platform === 'win32' ? 'java.exe' : 'java';
|
|
27
|
+
|
|
28
|
+
// 1. Check bundled JRE package
|
|
29
|
+
const jrePkg = `@vscjava/java-ls-jre-${process.platform}-${process.arch}`;
|
|
30
|
+
try {
|
|
31
|
+
const jrePkgDir = path.dirname(require.resolve(`${jrePkg}/package.json`));
|
|
32
|
+
const bundledJava = path.join(jrePkgDir, 'jre', 'bin', javaExeName);
|
|
33
|
+
if (fs.existsSync(bundledJava)) {
|
|
34
|
+
return bundledJava;
|
|
35
|
+
}
|
|
36
|
+
} catch {
|
|
37
|
+
// Package not installed, continue
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 2. Check config-specified java.home
|
|
41
|
+
if (config && config.java && config.java.home) {
|
|
42
|
+
const configJava = path.join(config.java.home, 'bin', javaExeName);
|
|
43
|
+
if (fs.existsSync(configJava)) {
|
|
44
|
+
return configJava;
|
|
45
|
+
}
|
|
46
|
+
console.error(`Warning: java.home in config points to ${config.java.home} but java not found there.`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 3. Check JAVA_HOME
|
|
50
|
+
if (process.env.JAVA_HOME) {
|
|
51
|
+
const javaHome = path.join(process.env.JAVA_HOME, 'bin', javaExeName);
|
|
52
|
+
if (fs.existsSync(javaHome)) {
|
|
53
|
+
return javaHome;
|
|
54
|
+
}
|
|
55
|
+
console.error(`Warning: JAVA_HOME is set to ${process.env.JAVA_HOME} but java not found there.`);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// 4. Fallback to PATH
|
|
59
|
+
return javaExeName;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Find the platform-specific configuration directory.
|
|
64
|
+
* Looks for the optional platform config npm package first,
|
|
65
|
+
* then falls back to a local config directory in the server folder.
|
|
66
|
+
*/
|
|
67
|
+
function findConfigDir() {
|
|
68
|
+
const platform = process.platform;
|
|
69
|
+
const configPkg = PLATFORM_PKG_MAP[platform];
|
|
70
|
+
|
|
71
|
+
if (!configPkg) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Try optional platform package
|
|
76
|
+
try {
|
|
77
|
+
const pkgDir = path.dirname(require.resolve(`${configPkg}/package.json`));
|
|
78
|
+
const configDirName = PLATFORM_CONFIG_MAP[platform];
|
|
79
|
+
const configDir = path.join(pkgDir, configDirName);
|
|
80
|
+
if (fs.existsSync(configDir)) {
|
|
81
|
+
return configDir;
|
|
82
|
+
}
|
|
83
|
+
} catch {
|
|
84
|
+
// Package not installed
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Fallback: look in server/ directory (for manual installs)
|
|
88
|
+
const serverDir = path.join(__dirname, '..', 'server');
|
|
89
|
+
const configDirName = PLATFORM_CONFIG_MAP[platform];
|
|
90
|
+
const localConfig = path.join(serverDir, configDirName);
|
|
91
|
+
if (fs.existsSync(localConfig)) {
|
|
92
|
+
return localConfig;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* Find the Eclipse Equinox launcher jar in the plugins directory.
|
|
100
|
+
*/
|
|
101
|
+
function findLauncherJar(serverDir) {
|
|
102
|
+
const pluginsDir = path.join(serverDir, 'plugins');
|
|
103
|
+
if (!fs.existsSync(pluginsDir)) {
|
|
104
|
+
return null;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const files = fs.readdirSync(pluginsDir);
|
|
108
|
+
const launcher = files.find(f => f.startsWith('org.eclipse.equinox.launcher_') && f.endsWith('.jar'));
|
|
109
|
+
if (launcher) {
|
|
110
|
+
return path.join(pluginsDir, launcher);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return null;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Get or create the workspace data directory.
|
|
118
|
+
* Each project gets its own workspace to avoid conflicts.
|
|
119
|
+
*/
|
|
120
|
+
function getWorkspaceDir(config) {
|
|
121
|
+
if (config && config.workspace && config.workspace.dataDir) {
|
|
122
|
+
return config.workspace.dataDir;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Create a unique workspace per project based on cwd hash
|
|
126
|
+
const crypto = require('crypto');
|
|
127
|
+
const cwd = config && config.repositoryPath ? config.repositoryPath : process.cwd();
|
|
128
|
+
const hash = crypto.createHash('md5').update(cwd).digest('hex').substring(0, 8);
|
|
129
|
+
const projectName = path.basename(cwd);
|
|
130
|
+
|
|
131
|
+
const dataDir = path.join(
|
|
132
|
+
process.env.HOME || process.env.USERPROFILE || '/tmp',
|
|
133
|
+
'.jdtls-workspace',
|
|
134
|
+
`${projectName}-${hash}`
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
if (!fs.existsSync(dataDir)) {
|
|
138
|
+
fs.mkdirSync(dataDir, { recursive: true });
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return dataDir;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get the path to the platform-specific native binary (if any).
|
|
146
|
+
*/
|
|
147
|
+
function getBinaryPath() {
|
|
148
|
+
// For Java LS, the "binary" is the java executable
|
|
149
|
+
return findJava({});
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
module.exports = {
|
|
153
|
+
findJava,
|
|
154
|
+
findConfigDir,
|
|
155
|
+
findLauncherJar,
|
|
156
|
+
getWorkspaceDir,
|
|
157
|
+
getBinaryPath,
|
|
158
|
+
PLATFORM_CONFIG_MAP,
|
|
159
|
+
PLATFORM_PKG_MAP,
|
|
160
|
+
};
|