zed-ets-language-server 2.3.3 → 3.0.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/index.js +81 -5
- package/lib/data-parser.js +17 -12
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import { spawn } from 'node:child_process';
|
|
4
4
|
import fs from 'node:fs';
|
|
5
|
+
import os from 'node:os';
|
|
6
|
+
import path from 'node:path';
|
|
5
7
|
import { logger } from './lib/logger.js';
|
|
6
8
|
import { parse } from './lib/data-parser.js';
|
|
7
9
|
import { listHelperPaths } from './lib/lib-expander.js'
|
|
@@ -9,6 +11,40 @@ import { listHelperPaths } from './lib/lib-expander.js'
|
|
|
9
11
|
// ETS language server path, passed by Rust extension process through environment variable
|
|
10
12
|
const etsLangServerPath = process.env.ETS_LANG_SERVER;
|
|
11
13
|
|
|
14
|
+
// The extension installs ohos-typescript next to @arkts/language-server, so a
|
|
15
|
+
// usable tsdk can be derived from the server path when settings don't name one:
|
|
16
|
+
// <work dir>/node_modules/@arkts/language-server/bin/ets-language-server.js
|
|
17
|
+
// <work dir>/node_modules/ohos-typescript/lib
|
|
18
|
+
// @arkts/language-server v1.3+ refuses to initialize unless ets.sdkPath points at
|
|
19
|
+
// a directory containing ets/build-tools/ets-loader/tsconfig.json (v1.2 accepted
|
|
20
|
+
// any value). Provide a minimal skeleton so the server starts without a real SDK;
|
|
21
|
+
// ArkUI typings degrade but TypeScript-level features keep working.
|
|
22
|
+
function ensurePlaceholderSdk() {
|
|
23
|
+
const sdkDir = path.join(os.tmpdir(), 'zed-ets-empty-ohos-sdk');
|
|
24
|
+
const etsLoaderDir = path.join(sdkDir, 'ets', 'build-tools', 'ets-loader');
|
|
25
|
+
try {
|
|
26
|
+
fs.mkdirSync(path.join(etsLoaderDir, 'declarations'), { recursive: true });
|
|
27
|
+
fs.mkdirSync(path.join(sdkDir, 'ets', 'component'), { recursive: true });
|
|
28
|
+
const tsconfigPath = path.join(etsLoaderDir, 'tsconfig.json');
|
|
29
|
+
if (!fs.existsSync(tsconfigPath)) {
|
|
30
|
+
fs.writeFileSync(tsconfigPath, '{}\n');
|
|
31
|
+
}
|
|
32
|
+
} catch (error) {
|
|
33
|
+
logger.error(`Failed to prepare placeholder SDK dir ${sdkDir}: ${error.message}`);
|
|
34
|
+
}
|
|
35
|
+
return sdkDir;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function detectTsdk() {
|
|
39
|
+
if (!etsLangServerPath) return undefined;
|
|
40
|
+
const serverBinDir = path.dirname(etsLangServerPath);
|
|
41
|
+
const candidates = [
|
|
42
|
+
path.join(serverBinDir, '..', '..', '..', 'ohos-typescript', 'lib'),
|
|
43
|
+
path.join(serverBinDir, '..', 'node_modules', 'ohos-typescript', 'lib'),
|
|
44
|
+
];
|
|
45
|
+
return candidates.find((dir) => fs.existsSync(path.join(dir, 'typescript.js')));
|
|
46
|
+
}
|
|
47
|
+
|
|
12
48
|
async function main() {
|
|
13
49
|
logger.section('🚀 ETS Language Server Wrapper');
|
|
14
50
|
|
|
@@ -39,33 +75,71 @@ async function main() {
|
|
|
39
75
|
logger.info(`Language server process exited, exit code: ${code}, signal: ${signal}`);
|
|
40
76
|
});
|
|
41
77
|
|
|
78
|
+
// Requests injected by this wrapper (not sent by the editor); their responses
|
|
79
|
+
// must not be forwarded to the editor, which never issued them.
|
|
80
|
+
const wrapperRequestIds = new Set();
|
|
81
|
+
|
|
42
82
|
// Set up forwarding of serverProcess IPC messages to process.stdout
|
|
43
83
|
serverProcess.on('message', (message) => {
|
|
84
|
+
if (message?.id !== undefined && wrapperRequestIds.delete(message.id)) {
|
|
85
|
+
logger.info(`Swallowed response to wrapper-injected request ${message.id}: ${JSON.stringify(message.result ?? message.error)}`);
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
44
88
|
// Convert IPC message to standard LSP format and send to stdout
|
|
45
89
|
const messageStr = JSON.stringify(message);
|
|
46
90
|
const headers = `Content-Length: ${Buffer.byteLength(messageStr)}\r\n\r\n`;
|
|
47
91
|
process.stdout.write(headers + messageStr);
|
|
48
92
|
});
|
|
49
93
|
|
|
50
|
-
// Set up forwarding of process.stdin to serverProcess IPC
|
|
51
|
-
|
|
94
|
+
// Set up forwarding of process.stdin to serverProcess IPC.
|
|
95
|
+
// No setEncoding here: the parser needs raw bytes because LSP Content-Length
|
|
96
|
+
// counts bytes, not characters.
|
|
52
97
|
process.stdin.on('data', (data) => parse(data, async (message) => {
|
|
53
98
|
// This special ets request is required in document: https://github.com/ohosvscode/arkTS/tree/next/packages/language-server
|
|
54
99
|
// When this goes wrong, ETS UI decorators and functions will be type of any
|
|
55
100
|
if (message.method === 'initialize') {
|
|
56
|
-
|
|
101
|
+
message.params = message.params ?? {};
|
|
102
|
+
const initializationOptions = message.params.initializationOptions ?? {};
|
|
103
|
+
message.params.initializationOptions = initializationOptions;
|
|
104
|
+
|
|
105
|
+
// Zed only passes initializationOptions when the user configured
|
|
106
|
+
// lsp.arkts-language-server.initialization_options in settings. Fall back to
|
|
107
|
+
// env vars, then auto-detection, so the server starts out of the box.
|
|
108
|
+
if (!initializationOptions.tsdk) {
|
|
109
|
+
initializationOptions.tsdk = process.env.ZED_ETS_TSDK || process.env.TSDK || detectTsdk();
|
|
110
|
+
logger.info(`No tsdk in initializationOptions; falling back to: ${initializationOptions.tsdk}`);
|
|
111
|
+
}
|
|
112
|
+
if (!initializationOptions.ohosSdkPath) {
|
|
113
|
+
initializationOptions.ohosSdkPath = process.env.ZED_ETS_OHOS_SDK_PATH || process.env.OHOS_SDK_PATH;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// The server cannot finish `initialize` without a tsdk (it fails loading
|
|
117
|
+
// TypeScript and Zed reports "Failed to start language server").
|
|
118
|
+
if (!initializationOptions.tsdk) {
|
|
119
|
+
logger.error(`No tsdk in LSP settings, env (ZED_ETS_TSDK/TSDK), or next to ${etsLangServerPath}; forwarding initialize as-is, the server will likely fail to start.`);
|
|
120
|
+
serverProcess.send(message);
|
|
121
|
+
return;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!initializationOptions.ohosSdkPath) {
|
|
125
|
+
initializationOptions.ohosSdkPath = ensurePlaceholderSdk();
|
|
126
|
+
logger.error('No ohosSdkPath in LSP settings or env (ZED_ETS_OHOS_SDK_PATH/OHOS_SDK_PATH); using a placeholder SDK skeleton, ArkUI SDK types will be unavailable until lsp.arkts-language-server.initialization_options.ohosSdkPath is set in Zed settings.');
|
|
127
|
+
}
|
|
57
128
|
|
|
58
129
|
const ohos = await listHelperPaths(initializationOptions.tsdk, initializationOptions.ohosSdkPath);
|
|
59
130
|
|
|
131
|
+
// Send both `ohos` and `ets` keys to stay compatible with
|
|
132
|
+
// @arkts/language-server v1.2.x (uses `ohos`) and v1.3.x+ (uses `ets`).
|
|
60
133
|
const etsSpecialRequest = {
|
|
61
134
|
jsonrpc: '2.0',
|
|
62
|
-
id: Date.now()
|
|
135
|
+
id: `zed-ets-wrapper-${Date.now()}`,
|
|
63
136
|
method: 'ets/waitForEtsConfigurationChangedRequested',
|
|
64
137
|
params: {
|
|
65
138
|
typescript: {
|
|
66
139
|
tsdk: initializationOptions.tsdk,
|
|
67
140
|
},
|
|
68
141
|
ohos: ohos,
|
|
142
|
+
ets: ohos,
|
|
69
143
|
},
|
|
70
144
|
};
|
|
71
145
|
|
|
@@ -74,10 +148,12 @@ async function main() {
|
|
|
74
148
|
tsdk: initializationOptions.tsdk,
|
|
75
149
|
};
|
|
76
150
|
generalInitRequest.params.initializationOptions.ohos = ohos;
|
|
151
|
+
generalInitRequest.params.initializationOptions.ets = ohos;
|
|
77
152
|
|
|
78
153
|
logger.info(JSON.stringify(generalInitRequest));
|
|
79
154
|
logger.info(JSON.stringify(etsSpecialRequest));
|
|
80
|
-
|
|
155
|
+
|
|
156
|
+
wrapperRequestIds.add(etsSpecialRequest.id);
|
|
81
157
|
serverProcess.send(generalInitRequest);
|
|
82
158
|
serverProcess.send(etsSpecialRequest);
|
|
83
159
|
return;
|
package/lib/data-parser.js
CHANGED
|
@@ -1,38 +1,43 @@
|
|
|
1
1
|
import { logger } from './logger.js';
|
|
2
2
|
|
|
3
|
-
let stdinBuffer =
|
|
3
|
+
let stdinBuffer = Buffer.alloc(0);
|
|
4
4
|
|
|
5
5
|
export function parse(data, callback) {
|
|
6
|
-
|
|
6
|
+
// LSP Content-Length is measured in bytes, not characters, so all parsing
|
|
7
|
+
// must operate on a Buffer. Convert string input to a Buffer if necessary
|
|
8
|
+
// (happens when stdin.setEncoding('utf8') is used, which yields strings).
|
|
9
|
+
const dataBuffer = Buffer.isBuffer(data) ? data : Buffer.from(data, 'utf8');
|
|
10
|
+
stdinBuffer = Buffer.concat([stdinBuffer, dataBuffer]);
|
|
7
11
|
|
|
8
12
|
while (true) {
|
|
9
|
-
// Find Content-Length header
|
|
10
|
-
const lengthMatch = stdinBuffer.match(/Content-Length: (\d+)\r\n/);
|
|
11
|
-
if (!lengthMatch) break;
|
|
12
|
-
|
|
13
|
-
const contentLength = Number.parseInt(lengthMatch[1]);
|
|
14
13
|
const headerEnd = stdinBuffer.indexOf('\r\n\r\n');
|
|
15
|
-
|
|
16
14
|
if (headerEnd === -1) break;
|
|
17
15
|
|
|
16
|
+
const headerPart = stdinBuffer.subarray(0, headerEnd).toString('utf8');
|
|
17
|
+
const lengthMatch = headerPart.match(/Content-Length: (\d+)/);
|
|
18
|
+
if (!lengthMatch) break;
|
|
19
|
+
|
|
20
|
+
const contentLength = Number.parseInt(lengthMatch[1]);
|
|
18
21
|
const messageStart = headerEnd + 4;
|
|
19
22
|
const messageEnd = messageStart + contentLength;
|
|
20
23
|
|
|
24
|
+
// Content-Length counts bytes, so compare against the buffer's byte length.
|
|
21
25
|
if (stdinBuffer.length < messageEnd) break;
|
|
22
26
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
stdinBuffer = stdinBuffer.substring(messageEnd);
|
|
27
|
+
const messageJson = stdinBuffer.subarray(messageStart, messageEnd).toString('utf8');
|
|
28
|
+
stdinBuffer = stdinBuffer.subarray(messageEnd);
|
|
26
29
|
|
|
27
30
|
try {
|
|
28
31
|
const message = JSON.parse(messageJson);
|
|
29
32
|
callback(message);
|
|
30
33
|
} catch (error) {
|
|
31
34
|
logger.error(`Error parsing message: ${error.message} ${error.stack} ${messageJson}`);
|
|
35
|
+
// Clear buffer on parse error to prevent corruption from leftover data
|
|
36
|
+
stdinBuffer = Buffer.alloc(0);
|
|
32
37
|
}
|
|
33
38
|
}
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
export function clearBuffer() {
|
|
37
|
-
stdinBuffer =
|
|
42
|
+
stdinBuffer = Buffer.alloc(0);
|
|
38
43
|
}
|