zed-ets-language-server 1.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 +186 -0
- package/package.json +17 -0
package/index.js
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { spawn } from 'node:child_process';
|
|
4
|
+
import fs from 'node:fs';
|
|
5
|
+
import path from 'node:path';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
// ETS language server path, passed by Rust extension process through environment variable
|
|
12
|
+
const etsLangServerPath = process.env.ETS_LANG_SERVER;
|
|
13
|
+
|
|
14
|
+
function createSimpleLogger() {
|
|
15
|
+
if (process.env.ZED_ETS_LANG_SERVER_LOG !== 'true') {
|
|
16
|
+
const noop = (_msg) => {};
|
|
17
|
+
return {
|
|
18
|
+
info: noop,
|
|
19
|
+
success: noop,
|
|
20
|
+
error: noop,
|
|
21
|
+
warn: noop,
|
|
22
|
+
section: noop,
|
|
23
|
+
data: noop,
|
|
24
|
+
close: noop,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
// Create log file write stream
|
|
28
|
+
const logFilePath = path.join(__dirname, 'arkts-lsw.log');
|
|
29
|
+
const logStream = fs.createWriteStream(logFilePath, { flags: 'w+' });
|
|
30
|
+
|
|
31
|
+
// Function to get current timestamp
|
|
32
|
+
const getTimestamp = () => {
|
|
33
|
+
const now = new Date();
|
|
34
|
+
return now.toISOString().slice(0, 19).replace('T', ' ');
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
// Logging utility
|
|
38
|
+
const logger = {
|
|
39
|
+
info: (msg) => {
|
|
40
|
+
const timestamp = getTimestamp();
|
|
41
|
+
const logMsg = `[${timestamp}] ℹ ${msg}\n`;
|
|
42
|
+
logStream.write(logMsg);
|
|
43
|
+
},
|
|
44
|
+
success: (msg) => {
|
|
45
|
+
const timestamp = getTimestamp();
|
|
46
|
+
const logMsg = `[${timestamp}] ✓ ${msg}\n`;
|
|
47
|
+
logStream.write(logMsg);
|
|
48
|
+
},
|
|
49
|
+
error: (msg) => {
|
|
50
|
+
const timestamp = getTimestamp();
|
|
51
|
+
const logMsg = `[${timestamp}] ✗ ${msg}\n`;
|
|
52
|
+
logStream.write(logMsg);
|
|
53
|
+
process.stderr.write(logMsg);
|
|
54
|
+
},
|
|
55
|
+
warn: (msg) => {
|
|
56
|
+
const timestamp = getTimestamp();
|
|
57
|
+
const logMsg = `[${timestamp}] ⚠ ${msg}\n`;
|
|
58
|
+
logStream.write(logMsg);
|
|
59
|
+
},
|
|
60
|
+
section: (msg) => {
|
|
61
|
+
const timestamp = getTimestamp();
|
|
62
|
+
const logMsg = `\n[${timestamp}] ${msg}\n\n`;
|
|
63
|
+
logStream.write(logMsg);
|
|
64
|
+
},
|
|
65
|
+
data: (label, data) => {
|
|
66
|
+
const timestamp = getTimestamp();
|
|
67
|
+
const logMsg = `[${timestamp}] ${label}: ${JSON.stringify(data, null, 2)}\n`;
|
|
68
|
+
logStream.write(logMsg);
|
|
69
|
+
},
|
|
70
|
+
// Add method to close log stream
|
|
71
|
+
close: () => {
|
|
72
|
+
logStream.end();
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
return logger;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function main() {
|
|
80
|
+
const logger = createSimpleLogger();
|
|
81
|
+
logger.section('🚀 ETS Language Server Wrapper');
|
|
82
|
+
|
|
83
|
+
// Check if language server exists
|
|
84
|
+
const serverExists = fs.existsSync(etsLangServerPath);
|
|
85
|
+
|
|
86
|
+
if (!serverExists) {
|
|
87
|
+
logger.error(`Language server does not exist, please build the language server first ${etsLangServerPath}`);
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
logger.success(`Language server path: ${etsLangServerPath}`);
|
|
92
|
+
|
|
93
|
+
// Start language server
|
|
94
|
+
logger.section('🔌 Starting Language Server');
|
|
95
|
+
|
|
96
|
+
const serverProcess = spawn('node', [etsLangServerPath, '--node-ipc', '--server-mode'], {
|
|
97
|
+
stdio: ['pipe', 'pipe', 'pipe', 'ipc'],
|
|
98
|
+
env: { ...process.env },
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
// Listen to server process error and exit events
|
|
102
|
+
serverProcess.on('error', (error) => {
|
|
103
|
+
logger.error(`Language server process error: ${error.message}`);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
serverProcess.on('exit', (code, signal) => {
|
|
107
|
+
logger.info(`Language server process exited, exit code: ${code}, signal: ${signal}`);
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
// Set up forwarding of serverProcess IPC messages to process.stdout
|
|
111
|
+
serverProcess.on('message', (message) => {
|
|
112
|
+
// Convert IPC message to standard LSP format and send to stdout
|
|
113
|
+
const messageStr = JSON.stringify(message);
|
|
114
|
+
const headers = `Content-Length: ${Buffer.byteLength(messageStr)}\r\n\r\n`;
|
|
115
|
+
process.stdout.write(headers + messageStr);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
// Set up forwarding of process.stdin to serverProcess IPC
|
|
119
|
+
let stdinBuffer = '';
|
|
120
|
+
process.stdin.on('data', (data) => {
|
|
121
|
+
stdinBuffer += data.toString();
|
|
122
|
+
|
|
123
|
+
while (true) {
|
|
124
|
+
// Find Content-Length header
|
|
125
|
+
const lengthMatch = stdinBuffer.match(/Content-Length: (\d+)\r\n/);
|
|
126
|
+
if (!lengthMatch) break;
|
|
127
|
+
|
|
128
|
+
const contentLength = Number.parseInt(lengthMatch[1]);
|
|
129
|
+
const headerEnd = stdinBuffer.indexOf('\r\n\r\n');
|
|
130
|
+
|
|
131
|
+
if (headerEnd === -1) break;
|
|
132
|
+
|
|
133
|
+
const messageStart = headerEnd + 4;
|
|
134
|
+
const messageEnd = messageStart + contentLength;
|
|
135
|
+
|
|
136
|
+
if (stdinBuffer.length < messageEnd) break;
|
|
137
|
+
|
|
138
|
+
// Extract message
|
|
139
|
+
const messageJson = stdinBuffer.substring(messageStart, messageEnd);
|
|
140
|
+
stdinBuffer = stdinBuffer.substring(messageEnd);
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
const message = JSON.parse(messageJson);
|
|
144
|
+
// Send message to language server via IPC
|
|
145
|
+
serverProcess.send(message);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
logger.error(`Failed to parse message from stdin: ${error.message}, ${messageJson}`);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Error handling
|
|
153
|
+
process.on('SIGTERM', () => {
|
|
154
|
+
logger.info('Received SIGTERM signal, shutting down language server...');
|
|
155
|
+
serverProcess.kill();
|
|
156
|
+
logger.close();
|
|
157
|
+
process.exit(0);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
process.on('SIGINT', () => {
|
|
161
|
+
logger.info('Received SIGINT signal, shutting down language server...');
|
|
162
|
+
serverProcess.kill();
|
|
163
|
+
logger.close();
|
|
164
|
+
process.exit(0);
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
logger.success('Language server wrapper started, beginning message forwarding');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Error handling
|
|
171
|
+
process.on('uncaughtException', (error) => {
|
|
172
|
+
logger.error(`Uncaught exception: ${error.message}`);
|
|
173
|
+
console.error(error);
|
|
174
|
+
logger.close(); // Close log stream
|
|
175
|
+
process.exit(1);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
process.on('unhandledRejection', (reason, _promise) => {
|
|
179
|
+
logger.error(`Unhandled Promise rejection: ${reason}`);
|
|
180
|
+
console.error(reason);
|
|
181
|
+
logger.close(); // Close log stream
|
|
182
|
+
process.exit(1);
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
// Run main function
|
|
186
|
+
main();
|
package/package.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "zed-ets-language-server",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "ETS language server wrapper for Zed ArkTS extension.",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"engines": {
|
|
7
|
+
"node": ">=22"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
11
|
+
},
|
|
12
|
+
"author": "liuyanghejerry <liuyanghejerry@126.com>",
|
|
13
|
+
"license": "MIT",
|
|
14
|
+
"dependencies": {
|
|
15
|
+
"@arkts/language-server": "^1.2.2"
|
|
16
|
+
}
|
|
17
|
+
}
|