@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.
Files changed (134) hide show
  1. package/README.md +102 -0
  2. package/bin/jdtls.js +282 -0
  3. package/lib/detect.js +121 -0
  4. package/lib/install.js +160 -0
  5. package/lib/postinstall.js +140 -0
  6. package/package.json +39 -0
  7. package/server/features/org.eclipse.equinox.executable_3.8.3200.v20260203-2149.jar +0 -0
  8. package/server/plugins/ch.qos.logback.classic_1.5.21.jar +0 -0
  9. package/server/plugins/ch.qos.logback.core_1.5.21.jar +0 -0
  10. package/server/plugins/com.google.gson_2.13.2.jar +0 -0
  11. package/server/plugins/com.google.guava.failureaccess_1.0.3.jar +0 -0
  12. package/server/plugins/com.google.guava_33.5.0.jre.jar +0 -0
  13. package/server/plugins/com.sun.jna.platform_5.18.1.jar +0 -0
  14. package/server/plugins/com.sun.jna_5.18.1.v20251001-0800.jar +0 -0
  15. package/server/plugins/jakarta.annotation-api_1.3.5.jar +0 -0
  16. package/server/plugins/jakarta.inject.jakarta.inject-api_1.0.5.jar +0 -0
  17. package/server/plugins/jakarta.servlet-api_6.1.0.jar +0 -0
  18. package/server/plugins/org.apache.ant_1.10.15.v20240901-1000.jar +0 -0
  19. package/server/plugins/org.apache.aries.spifly.dynamic.bundle_1.3.7.jar +0 -0
  20. package/server/plugins/org.apache.commons.cli_1.11.0.jar +0 -0
  21. package/server/plugins/org.apache.commons.commons-codec_1.20.0.jar +0 -0
  22. package/server/plugins/org.apache.commons.lang3_3.20.0.jar +0 -0
  23. package/server/plugins/org.apache.felix.scr_2.2.14.jar +0 -0
  24. package/server/plugins/org.commonmark.ext-gfm-tables_0.27.1.jar +0 -0
  25. package/server/plugins/org.commonmark_0.27.1.jar +0 -0
  26. package/server/plugins/org.eclipse.ant.core_3.7.800.v20260130-1053.jar +0 -0
  27. package/server/plugins/org.eclipse.buildship.compat_3.1.10.v20250827-0209-s.jar +0 -0
  28. package/server/plugins/org.eclipse.buildship.core_3.1.10.v20250827-0209-s.jar +0 -0
  29. package/server/plugins/org.eclipse.compare.core_3.8.800.v20250718-1505.jar +0 -0
  30. package/server/plugins/org.eclipse.core.commands_3.12.500.v20251103-0733.jar +0 -0
  31. package/server/plugins/org.eclipse.core.contenttype_3.9.800.v20251105-1620.jar +0 -0
  32. package/server/plugins/org.eclipse.core.expressions_3.9.500.v20250608-0434.jar +0 -0
  33. package/server/plugins/org.eclipse.core.filebuffers_3.8.500.v20251103-0746.jar +0 -0
  34. package/server/plugins/org.eclipse.core.filesystem_1.11.400.v20251107-0507.jar +0 -0
  35. package/server/plugins/org.eclipse.core.jobs_3.15.700.v20250725-1147.jar +0 -0
  36. package/server/plugins/org.eclipse.core.net_1.5.800.v20250613-1119.jar +0 -0
  37. package/server/plugins/org.eclipse.core.resources_3.23.200.v20251217-0810.jar +0 -0
  38. package/server/plugins/org.eclipse.core.runtime_3.34.200.v20251220-0953.jar +0 -0
  39. package/server/plugins/org.eclipse.core.variables_3.6.700.v20250913-1442.jar +0 -0
  40. package/server/plugins/org.eclipse.debug.core_3.23.200.v20251107-0507.jar +0 -0
  41. package/server/plugins/org.eclipse.equinox.app_1.7.600.v20251211-1038.jar +0 -0
  42. package/server/plugins/org.eclipse.equinox.common_3.20.300.v20251111-0312.jar +0 -0
  43. package/server/plugins/org.eclipse.equinox.frameworkadmin.equinox_1.3.400.v20250515-0513.jar +0 -0
  44. package/server/plugins/org.eclipse.equinox.frameworkadmin_2.3.500.v20250716-0529.jar +0 -0
  45. package/server/plugins/org.eclipse.equinox.http.service.api_1.2.102.v20250520-0629.jar +0 -0
  46. package/server/plugins/org.eclipse.equinox.launcher.cocoa.macosx.aarch64_1.2.1400.v20250801-0854.jar +0 -0
  47. package/server/plugins/org.eclipse.equinox.launcher.cocoa.macosx.x86_64_1.2.1400.v20250801-0854.jar +0 -0
  48. package/server/plugins/org.eclipse.equinox.launcher.gtk.linux.aarch64_1.2.1500.v20250801-0854.jar +0 -0
  49. package/server/plugins/org.eclipse.equinox.launcher.gtk.linux.x86_64_1.2.1500.v20250801-0854.jar +0 -0
  50. package/server/plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.3.0.v20260203-2149.jar +0 -0
  51. package/server/plugins/org.eclipse.equinox.launcher_1.7.100.v20251111-0406.jar +0 -0
  52. package/server/plugins/org.eclipse.equinox.preferences_3.12.100.v20251111-0704.jar +0 -0
  53. package/server/plugins/org.eclipse.equinox.registry_3.12.600.v20250906-0651.jar +0 -0
  54. package/server/plugins/org.eclipse.equinox.security.linux_1.1.400.v20250521-0415.jar +0 -0
  55. package/server/plugins/org.eclipse.equinox.security.macosx_1.102.500.v20250521-0414.jar +0 -0
  56. package/server/plugins/org.eclipse.equinox.security.win32_1.3.0.v20240419-2334.jar +0 -0
  57. package/server/plugins/org.eclipse.equinox.security_1.4.700.v20250622-1644.jar +0 -0
  58. package/server/plugins/org.eclipse.equinox.simpleconfigurator.manipulator_2.3.600.v20250729-0655.jar +0 -0
  59. package/server/plugins/org.eclipse.equinox.simpleconfigurator_1.5.700.v20251111-1031.jar +0 -0
  60. package/server/plugins/org.eclipse.jdt.apt.core_3.8.600.v20241001-0914.jar +0 -0
  61. package/server/plugins/org.eclipse.jdt.apt.pluggable.core_1.4.700.v20251202-0459.jar +0 -0
  62. package/server/plugins/org.eclipse.jdt.core.compiler.batch_3.45.0.v20260224-0835.jar +0 -0
  63. package/server/plugins/org.eclipse.jdt.core.javac_1.0.0.z20260225-2209.jar +0 -0
  64. package/server/plugins/org.eclipse.jdt.core.manipulation_1.24.0.v20260212-2209.jar +0 -0
  65. package/server/plugins/org.eclipse.jdt.core_3.45.0.v20260219-1233.jar +0 -0
  66. package/server/plugins/org.eclipse.jdt.debug_3.25.100.v20260212-0641.jar +0 -0
  67. package/server/plugins/org.eclipse.jdt.junit.core_3.14.100.v20251223-2158.jar +0 -0
  68. package/server/plugins/org.eclipse.jdt.junit.runtime_3.8.0.v20251113-1434.jar +0 -0
  69. package/server/plugins/org.eclipse.jdt.launching.macosx_3.6.400.v20260211-1052.jar +0 -0
  70. package/server/plugins/org.eclipse.jdt.launching_3.24.100.v20260218-1303.jar +0 -0
  71. package/server/plugins/org.eclipse.jdt.ls.core_1.57.0.202602261110.jar +0 -0
  72. package/server/plugins/org.eclipse.jdt.ls.filesystem_1.57.0.202602261110.jar +0 -0
  73. package/server/plugins/org.eclipse.jdt.ls.logback.appender_1.57.0.202602261110.jar +0 -0
  74. package/server/plugins/org.eclipse.jetty.servlet-api_4.0.9.jar +0 -0
  75. package/server/plugins/org.eclipse.lsp4j.jsonrpc_0.24.0.v20250131-1745.jar +0 -0
  76. package/server/plugins/org.eclipse.lsp4j_0.24.0.v20250131-1745.jar +0 -0
  77. package/server/plugins/org.eclipse.ltk.core.refactoring_3.15.100.v20251023-1358.jar +0 -0
  78. package/server/plugins/org.eclipse.m2e.apt.core_2.3.100.20250418-1315.jar +0 -0
  79. package/server/plugins/org.eclipse.m2e.core_2.7.600.20251121-1832.jar +0 -0
  80. package/server/plugins/org.eclipse.m2e.jdt_2.5.0.20251112-1507.jar +0 -0
  81. package/server/plugins/org.eclipse.m2e.maven.runtime_3.9.1101.20251020-1549.jar +0 -0
  82. package/server/plugins/org.eclipse.m2e.workspace.cli_0.4.0.jar +0 -0
  83. package/server/plugins/org.eclipse.osgi.compatibility.state_1.3.0.v20251022-1724.jar +0 -0
  84. package/server/plugins/org.eclipse.osgi.services_3.12.300.v20250707-1221.jar +0 -0
  85. package/server/plugins/org.eclipse.osgi_3.24.100.v20251215-1416.jar +0 -0
  86. package/server/plugins/org.eclipse.search.core_3.16.600.v20250920-0652.jar +0 -0
  87. package/server/plugins/org.eclipse.text_3.14.600.v20260112-1806.jar +0 -0
  88. package/server/plugins/org.eclipse.xtext.xbase.lib_2.41.0.v20251124-0739.jar +0 -0
  89. package/server/plugins/org.gradle.toolingapi_8.9.0.v20250827-0209-s.jar +0 -0
  90. package/server/plugins/org.hamcrest_3.0.0.jar +0 -0
  91. package/server/plugins/org.jsoup_1.19.1.jar +0 -0
  92. package/server/plugins/org.junit_4.13.2.v20240929-1000.jar +0 -0
  93. package/server/plugins/org.objectweb.asm.commons_9.9.1.jar +0 -0
  94. package/server/plugins/org.objectweb.asm.tree.analysis_9.9.1.jar +0 -0
  95. package/server/plugins/org.objectweb.asm.tree_9.9.1.jar +0 -0
  96. package/server/plugins/org.objectweb.asm.util_9.9.1.jar +0 -0
  97. package/server/plugins/org.objectweb.asm_9.9.1.jar +0 -0
  98. package/server/plugins/org.osgi.service.cm_1.6.1.202109301733.jar +0 -0
  99. package/server/plugins/org.osgi.service.component_1.5.1.202212101352.jar +0 -0
  100. package/server/plugins/org.osgi.service.device_1.1.1.202109301733.jar +0 -0
  101. package/server/plugins/org.osgi.service.event_1.4.1.202109301733.jar +0 -0
  102. package/server/plugins/org.osgi.service.http.whiteboard_1.1.1.202109301733.jar +0 -0
  103. package/server/plugins/org.osgi.service.metatype_1.4.1.202109301733.jar +0 -0
  104. package/server/plugins/org.osgi.service.prefs_1.1.2.202109301733.jar +0 -0
  105. package/server/plugins/org.osgi.service.provisioning_1.2.0.201505202024.jar +0 -0
  106. package/server/plugins/org.osgi.service.upnp_1.2.1.202109301733.jar +0 -0
  107. package/server/plugins/org.osgi.service.useradmin_1.1.1.202109301733.jar +0 -0
  108. package/server/plugins/org.osgi.service.wireadmin_1.0.2.202109301733.jar +0 -0
  109. package/server/plugins/org.osgi.util.function_1.2.0.202109301733.jar +0 -0
  110. package/server/plugins/org.osgi.util.promise_1.3.0.202212101352.jar +0 -0
  111. package/server/plugins/slf4j.api_2.0.17.jar +0 -0
  112. package/server/plugins/wrapped.com.jetbrains.intellij.java.java-decompiler-engine_253.29346.240.jar +0 -0
  113. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-ext-emoji_0.64.8.jar +0 -0
  114. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-ext-gfm-strikethrough_0.64.8.jar +0 -0
  115. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-ext-ins_0.64.8.jar +0 -0
  116. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-ext-superscript_0.64.8.jar +0 -0
  117. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-ext-tables_0.64.8.jar +0 -0
  118. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-ext-wikilink_0.64.8.jar +0 -0
  119. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-html2md-converter_0.64.8.jar +0 -0
  120. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-jira-converter_0.64.8.jar +0 -0
  121. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-ast_0.64.8.jar +0 -0
  122. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-builder_0.64.8.jar +0 -0
  123. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-collection_0.64.8.jar +0 -0
  124. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-data_0.64.8.jar +0 -0
  125. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-dependency_0.64.8.jar +0 -0
  126. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-format_0.64.8.jar +0 -0
  127. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-html_0.64.8.jar +0 -0
  128. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-misc_0.64.8.jar +0 -0
  129. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-options_0.64.8.jar +0 -0
  130. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-sequence_0.64.8.jar +0 -0
  131. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util-visitor_0.64.8.jar +0 -0
  132. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark-util_0.64.8.jar +0 -0
  133. package/server/plugins/wrapped.com.vladsch.flexmark.flexmark_0.64.8.jar +0 -0
  134. 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
+ };