glimmervoid 0.0.0-stage → 0.30.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/LICENSE +48 -0
- package/README.md +195 -2
- package/dist/bin/glimmervoid.js +275 -0
- package/dist/chunks/adapters-Dho1VSY5.js +2 -0
- package/dist/chunks/adapters-zPDx1Xox.js +575 -0
- package/dist/chunks/agent-api-cli-D0MmtEdz.js +71 -0
- package/dist/chunks/agent-api-core-Dz0r21y9.js +103 -0
- package/dist/chunks/agent-setup-cli-B5Atxr3F.js +139 -0
- package/dist/chunks/ascii-figure-core-pDAwtlKO.js +45 -0
- package/dist/chunks/child-process-safe-DRGsp0TE.js +69 -0
- package/dist/chunks/child-process-safe-DvQjXsgz.js +2 -0
- package/dist/chunks/config-path-core-DFt-TemS.js +38 -0
- package/dist/chunks/config-store-CC8T3okC.js +2 -0
- package/dist/chunks/config-store-mm7MgzoL.js +1412 -0
- package/dist/chunks/contracts-DGkKNh9W.js +3020 -0
- package/dist/chunks/grok-DuU8xVLZ.js +436 -0
- package/dist/chunks/hook-command-core-DclEoE9e.js +14 -0
- package/dist/chunks/hook-relay-core-BHfYFnYV.js +88 -0
- package/dist/chunks/hook-tools-pSJia7ks.js +74 -0
- package/dist/chunks/ingest-core-DWI1Mbu4.js +342 -0
- package/dist/chunks/ingest-editor-core-BuI1gMF_.js +445 -0
- package/dist/chunks/loopback-post-C7C22eYi.js +79 -0
- package/dist/chunks/main-CuIZQPhZ.js +43559 -0
- package/dist/chunks/node-pty-preflight-CK1knGIR.js +162 -0
- package/dist/chunks/pair-cli-w-gCAF-1.js +110 -0
- package/dist/chunks/path-doctor-C23VAqeu.js +63 -0
- package/dist/chunks/refocus-core-BKQKllq7.js +38 -0
- package/dist/chunks/relay-stdin-CpHvLu6M.js +20 -0
- package/dist/chunks/remote-config-C2ytQwrs.js +525 -0
- package/dist/chunks/rtk-hook-core-Bl6gYHPN.js +133 -0
- package/dist/chunks/rtk-resolver-CLo-gZzw.js +21 -0
- package/dist/chunks/rtk-resolver-CMQAidlI.js +2 -0
- package/dist/chunks/runtime-paths-Ci_ksSkj.js +75 -0
- package/dist/chunks/runtime-paths-JLrZ3wyi.js +2 -0
- package/dist/chunks/sandbox-deps-Dd5-kZnf.js +67 -0
- package/dist/chunks/session-Cez8aIWg.js +164 -0
- package/dist/chunks/spawn-command-Cc40FiZI.js +134 -0
- package/dist/chunks/visions-cli-BXQFIBCX.js +98 -0
- package/dist/chunks/visions-setup-CvVnOe1r.js +1753 -0
- package/dist/client/assets/favicon-CKygeLwW.svg +7 -0
- package/dist/client/assets/index-B1MFijjb.css +1 -0
- package/dist/client/assets/index-CU1iPPdn.js +249 -0
- package/dist/client/index.html +108 -0
- package/dist/package.json +3 -0
- package/dist/scripts/postinstall-path-check.js +36 -0
- package/dist/server/index.js +92 -0
- package/dist/session/command-hook-relay.js +16 -0
- package/dist/session/hook-relay.js +24 -0
- package/dist/session/hook-tool-relay.js +107 -0
- package/dist/session/statusline-relay.js +82 -0
- package/dist/session/visions-relay.js +627 -0
- package/dist/tools/vscode-visions/extension.js +289 -0
- package/dist/tools/vscode-visions/lsp-convert.js +80 -0
- package/dist/tools/vscode-visions/package.json +32 -0
- package/dist/tools/vscode-visions/visions-lsp-core.js +112 -0
- package/package.json +96 -4
- package/scripts/postinstall.mjs +4 -0
- package/scripts/prepare-build.js +38 -0
- package/scripts/recover-handoff.mjs +82 -0
|
@@ -0,0 +1,627 @@
|
|
|
1
|
+
import { n as glimmervoidHomeDir, t as decideConfigPath } from "../chunks/config-path-core-DFt-TemS.js";
|
|
2
|
+
import { _ as listDocs, b as uriOfParams, c as applyDidChange, d as createDocStore, l as applyDidClose, m as getDoc, p as formatRange, t as ACTIVITY_METHOD, u as applyDidOpen } from "../chunks/ingest-editor-core-BuI1gMF_.js";
|
|
3
|
+
import fs from "node:fs";
|
|
4
|
+
import os from "node:os";
|
|
5
|
+
import WebSocket from "ws";
|
|
6
|
+
//#region server/core/visions-lsp-core.ts
|
|
7
|
+
var HEADER_END = "\r\n\r\n";
|
|
8
|
+
var CONTENT_LENGTH_RE = /^content-length:\s*(\d+)\s*$/i;
|
|
9
|
+
function createParserState() {
|
|
10
|
+
return {
|
|
11
|
+
buffer: Buffer.alloc(0),
|
|
12
|
+
neededBodyBytes: null
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
function feedFrameBytes(state, chunk) {
|
|
16
|
+
const nextState = state || createParserState();
|
|
17
|
+
nextState.buffer = Buffer.concat([nextState.buffer, chunk]);
|
|
18
|
+
const messages = [];
|
|
19
|
+
while (true) {
|
|
20
|
+
const bodyBytes = readNeededBodyBytes(nextState);
|
|
21
|
+
if (bodyBytes === null) break;
|
|
22
|
+
if (typeof bodyBytes !== "number") {
|
|
23
|
+
messages.push(bodyBytes);
|
|
24
|
+
nextState.neededBodyBytes = null;
|
|
25
|
+
continue;
|
|
26
|
+
}
|
|
27
|
+
if (nextState.buffer.length < bodyBytes) break;
|
|
28
|
+
const body = nextState.buffer.subarray(0, bodyBytes);
|
|
29
|
+
nextState.buffer = nextState.buffer.subarray(bodyBytes);
|
|
30
|
+
nextState.neededBodyBytes = null;
|
|
31
|
+
messages.push(parseBody(body));
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
state: nextState,
|
|
35
|
+
messages
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
function readNeededBodyBytes(state) {
|
|
39
|
+
if (state.neededBodyBytes !== null) return state.neededBodyBytes;
|
|
40
|
+
const headerEndAt = state.buffer.indexOf(HEADER_END);
|
|
41
|
+
if (headerEndAt === -1) return null;
|
|
42
|
+
const headerText = state.buffer.subarray(0, headerEndAt).toString("ascii");
|
|
43
|
+
state.buffer = state.buffer.subarray(headerEndAt + 4);
|
|
44
|
+
const contentLength = contentLengthFromHeader(headerText);
|
|
45
|
+
if (contentLength === null) {
|
|
46
|
+
state.buffer = Buffer.alloc(0);
|
|
47
|
+
return {
|
|
48
|
+
parseError: true,
|
|
49
|
+
reason: "missing-content-length",
|
|
50
|
+
raw: headerText
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
state.neededBodyBytes = contentLength;
|
|
54
|
+
return state.neededBodyBytes;
|
|
55
|
+
}
|
|
56
|
+
function contentLengthFromHeader(headerText) {
|
|
57
|
+
const lines = headerText.split("\r\n");
|
|
58
|
+
for (const line of lines) {
|
|
59
|
+
const match = line.match(CONTENT_LENGTH_RE);
|
|
60
|
+
if (!match) continue;
|
|
61
|
+
return Number(match[1]);
|
|
62
|
+
}
|
|
63
|
+
return null;
|
|
64
|
+
}
|
|
65
|
+
function parseBody(body) {
|
|
66
|
+
const raw = body.toString("utf8");
|
|
67
|
+
try {
|
|
68
|
+
return JSON.parse(raw);
|
|
69
|
+
} catch {
|
|
70
|
+
return {
|
|
71
|
+
parseError: true,
|
|
72
|
+
raw
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
function serializeFrame(messageObject) {
|
|
77
|
+
const body = JSON.stringify(messageObject);
|
|
78
|
+
const bodyBytes = Buffer.byteLength(body, "utf8");
|
|
79
|
+
return Buffer.from(`Content-Length: ${bodyBytes}\r\n\r\n${body}`, "utf8");
|
|
80
|
+
}
|
|
81
|
+
function classifyMessage(msg) {
|
|
82
|
+
if (!msg || typeof msg !== "object") return {
|
|
83
|
+
kind: "invalid",
|
|
84
|
+
method: void 0,
|
|
85
|
+
id: void 0
|
|
86
|
+
};
|
|
87
|
+
const fields = msg;
|
|
88
|
+
const hasMethod = typeof fields.method === "string";
|
|
89
|
+
const hasId = Object.hasOwn(msg, "id");
|
|
90
|
+
const method = typeof fields.method === "string" ? fields.method : void 0;
|
|
91
|
+
if (hasMethod && hasId) return {
|
|
92
|
+
kind: "request",
|
|
93
|
+
method,
|
|
94
|
+
id: fields.id
|
|
95
|
+
};
|
|
96
|
+
if (hasMethod) return {
|
|
97
|
+
kind: "notification",
|
|
98
|
+
method,
|
|
99
|
+
id: void 0
|
|
100
|
+
};
|
|
101
|
+
if (hasId) return {
|
|
102
|
+
kind: "response",
|
|
103
|
+
method: void 0,
|
|
104
|
+
id: fields.id
|
|
105
|
+
};
|
|
106
|
+
return {
|
|
107
|
+
kind: "invalid",
|
|
108
|
+
method: void 0,
|
|
109
|
+
id: void 0
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
//#endregion
|
|
113
|
+
//#region session/core/visions-relay-core.ts
|
|
114
|
+
var MAX_DAEMON_FRAME_BYTES = 2097152;
|
|
115
|
+
function daemonMessage(method, params) {
|
|
116
|
+
return {
|
|
117
|
+
type: "lsp",
|
|
118
|
+
method,
|
|
119
|
+
params
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function replayDidOpenMessage(doc) {
|
|
123
|
+
return daemonMessage("textDocument/didOpen", { textDocument: {
|
|
124
|
+
uri: doc.uri,
|
|
125
|
+
languageId: doc.languageId,
|
|
126
|
+
version: doc.version,
|
|
127
|
+
text: doc.text
|
|
128
|
+
} });
|
|
129
|
+
}
|
|
130
|
+
function decideDaemonFrame(payload, maxFrameBytes = MAX_DAEMON_FRAME_BYTES) {
|
|
131
|
+
const serialized = JSON.stringify(payload);
|
|
132
|
+
const frameBytes = Buffer.byteLength(serialized, "utf8");
|
|
133
|
+
return {
|
|
134
|
+
send: frameBytes <= maxFrameBytes,
|
|
135
|
+
serialized,
|
|
136
|
+
frameBytes
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function planMirrorReplay(documents, maxFrameBytes = MAX_DAEMON_FRAME_BYTES) {
|
|
140
|
+
const frames = [];
|
|
141
|
+
const skipped = [];
|
|
142
|
+
const mirrorDocuments = Array.isArray(documents) ? documents : [];
|
|
143
|
+
for (const doc of mirrorDocuments) {
|
|
144
|
+
const message = replayDidOpenMessage(doc);
|
|
145
|
+
const decision = decideDaemonFrame(message, maxFrameBytes);
|
|
146
|
+
if (!decision.send) {
|
|
147
|
+
skipped.push({
|
|
148
|
+
uri: doc.uri,
|
|
149
|
+
frameBytes: decision.frameBytes
|
|
150
|
+
});
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
frames.push({
|
|
154
|
+
uri: doc.uri,
|
|
155
|
+
message,
|
|
156
|
+
serialized: decision.serialized
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
frames,
|
|
161
|
+
skipped
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function decideMirrorSync({ method, isUnsynced, originalFrameFits, fullFrameFits }) {
|
|
165
|
+
if (method === "textDocument/didClose") return {
|
|
166
|
+
actions: isUnsynced ? [] : ["original"],
|
|
167
|
+
forgetUnsynced: true,
|
|
168
|
+
markUnsynced: false,
|
|
169
|
+
shouldLog: false
|
|
170
|
+
};
|
|
171
|
+
if (isUnsynced && !fullFrameFits) return {
|
|
172
|
+
actions: [],
|
|
173
|
+
forgetUnsynced: false,
|
|
174
|
+
markUnsynced: false,
|
|
175
|
+
shouldLog: false
|
|
176
|
+
};
|
|
177
|
+
if (isUnsynced) return {
|
|
178
|
+
actions: ["full-open"],
|
|
179
|
+
forgetUnsynced: false,
|
|
180
|
+
markUnsynced: false,
|
|
181
|
+
shouldLog: false
|
|
182
|
+
};
|
|
183
|
+
if (originalFrameFits) return {
|
|
184
|
+
actions: ["original"],
|
|
185
|
+
forgetUnsynced: false,
|
|
186
|
+
markUnsynced: false,
|
|
187
|
+
shouldLog: false
|
|
188
|
+
};
|
|
189
|
+
return {
|
|
190
|
+
actions: fullFrameFits ? ["close", "full-open"] : ["close"],
|
|
191
|
+
forgetUnsynced: false,
|
|
192
|
+
markUnsynced: true,
|
|
193
|
+
shouldLog: true
|
|
194
|
+
};
|
|
195
|
+
}
|
|
196
|
+
//#endregion
|
|
197
|
+
//#region session/visions-relay.ts
|
|
198
|
+
var DEFAULT_PORTS = [5173, 3e3];
|
|
199
|
+
var MAX_PENDING_FORWARDS = 50;
|
|
200
|
+
var INITIAL_RETRY_MS = 500;
|
|
201
|
+
var MAX_RETRY_MS = 5e3;
|
|
202
|
+
var STABLE_CONNECTION_MS = 5e3;
|
|
203
|
+
var METHOD_NOT_FOUND = -32601;
|
|
204
|
+
var MIRROR_METHODS = /* @__PURE__ */ new Set([
|
|
205
|
+
"textDocument/didOpen",
|
|
206
|
+
"textDocument/didChange",
|
|
207
|
+
"textDocument/didClose"
|
|
208
|
+
]);
|
|
209
|
+
var FORWARDED_METHODS = /* @__PURE__ */ new Set([
|
|
210
|
+
...MIRROR_METHODS,
|
|
211
|
+
"textDocument/didSave",
|
|
212
|
+
ACTIVITY_METHOD
|
|
213
|
+
]);
|
|
214
|
+
var CODE_ACTION_METHOD = "textDocument/codeAction";
|
|
215
|
+
var APPLY_EDIT_METHOD = "workspace/applyEdit";
|
|
216
|
+
var CODE_ACTION_TIMEOUT_MS = 2e3;
|
|
217
|
+
function asDocumentParams(params) {
|
|
218
|
+
if (params && typeof params === "object" && !Array.isArray(params)) return params;
|
|
219
|
+
return null;
|
|
220
|
+
}
|
|
221
|
+
function parsePortValue(value) {
|
|
222
|
+
const port = Number(value);
|
|
223
|
+
if (!Number.isInteger(port) || port < 1 || port > 65535) return null;
|
|
224
|
+
return port;
|
|
225
|
+
}
|
|
226
|
+
function resolvePortPlan(argv = [], env = process.env, configPort = null) {
|
|
227
|
+
const flagIndex = argv.indexOf("--port");
|
|
228
|
+
const flagPort = flagIndex >= 0 ? parsePortValue(argv[flagIndex + 1]) : null;
|
|
229
|
+
if (flagPort !== null) return {
|
|
230
|
+
ports: [flagPort],
|
|
231
|
+
isFixed: true
|
|
232
|
+
};
|
|
233
|
+
const envPort = parsePortValue(env.GLIMMERVOID_PORT);
|
|
234
|
+
if (envPort !== null) return {
|
|
235
|
+
ports: [envPort],
|
|
236
|
+
isFixed: true
|
|
237
|
+
};
|
|
238
|
+
const configured = parsePortValue(configPort);
|
|
239
|
+
if (configured === null) return {
|
|
240
|
+
ports: DEFAULT_PORTS.slice(),
|
|
241
|
+
isFixed: false
|
|
242
|
+
};
|
|
243
|
+
return {
|
|
244
|
+
ports: [configured, ...DEFAULT_PORTS.filter((port) => port !== configured)],
|
|
245
|
+
isFixed: false
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function readConfiguredPort(env = process.env, fsApi = fs) {
|
|
249
|
+
const decided = decideConfigPath({
|
|
250
|
+
env,
|
|
251
|
+
homeDir: glimmervoidHomeDir(os.homedir(), env)
|
|
252
|
+
}, (candidate) => {
|
|
253
|
+
try {
|
|
254
|
+
return fsApi.existsSync(candidate);
|
|
255
|
+
} catch {
|
|
256
|
+
return false;
|
|
257
|
+
}
|
|
258
|
+
});
|
|
259
|
+
if (!decided.path) return null;
|
|
260
|
+
try {
|
|
261
|
+
const parsed = JSON.parse(fsApi.readFileSync(decided.path, "utf8"));
|
|
262
|
+
if (parsed && typeof parsed === "object") return parsed.port ?? null;
|
|
263
|
+
return null;
|
|
264
|
+
} catch {
|
|
265
|
+
return null;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
function nextDelayMs(currentDelayMs) {
|
|
269
|
+
return Math.min(currentDelayMs * 2, MAX_RETRY_MS);
|
|
270
|
+
}
|
|
271
|
+
var SYNC_KIND_INCREMENTAL = 2;
|
|
272
|
+
function initializeResult() {
|
|
273
|
+
return {
|
|
274
|
+
capabilities: {
|
|
275
|
+
textDocumentSync: {
|
|
276
|
+
openClose: true,
|
|
277
|
+
change: 2
|
|
278
|
+
},
|
|
279
|
+
codeActionProvider: true
|
|
280
|
+
},
|
|
281
|
+
serverInfo: { name: "glimmervoid-visions" }
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
function methodNotFoundResponse(id, method) {
|
|
285
|
+
return {
|
|
286
|
+
jsonrpc: "2.0",
|
|
287
|
+
id,
|
|
288
|
+
error: {
|
|
289
|
+
code: METHOD_NOT_FOUND,
|
|
290
|
+
message: "Method not found",
|
|
291
|
+
data: { method }
|
|
292
|
+
}
|
|
293
|
+
};
|
|
294
|
+
}
|
|
295
|
+
function responseMessage(id, result) {
|
|
296
|
+
return {
|
|
297
|
+
jsonrpc: "2.0",
|
|
298
|
+
id,
|
|
299
|
+
result
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
function editorNotification(method, params) {
|
|
303
|
+
return {
|
|
304
|
+
jsonrpc: "2.0",
|
|
305
|
+
method,
|
|
306
|
+
params
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
function daemonRequest(id, method, params) {
|
|
310
|
+
return {
|
|
311
|
+
type: "lsp-request",
|
|
312
|
+
id,
|
|
313
|
+
method,
|
|
314
|
+
params
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
function daemonResponse(id, result) {
|
|
318
|
+
return {
|
|
319
|
+
type: "lsp-response",
|
|
320
|
+
id,
|
|
321
|
+
result
|
|
322
|
+
};
|
|
323
|
+
}
|
|
324
|
+
function editorRequest(id, method, params) {
|
|
325
|
+
return {
|
|
326
|
+
jsonrpc: "2.0",
|
|
327
|
+
id,
|
|
328
|
+
method,
|
|
329
|
+
params
|
|
330
|
+
};
|
|
331
|
+
}
|
|
332
|
+
function mirrorFailureDetail(update) {
|
|
333
|
+
if (update.reason === "invalid-range" || update.reason === "invalid-text") return ` change=${update.index} range=${formatRange(update.range)}`;
|
|
334
|
+
if (update.reason === "stale-version") return ` version=${update.version} current=${update.currentVersion}`;
|
|
335
|
+
return "";
|
|
336
|
+
}
|
|
337
|
+
function sendWsFrame(ws, decision) {
|
|
338
|
+
if (!ws || ws.readyState !== WebSocket.OPEN) return false;
|
|
339
|
+
if (!decision.send) return false;
|
|
340
|
+
ws.send(decision.serialized);
|
|
341
|
+
return true;
|
|
342
|
+
}
|
|
343
|
+
function sendWsJson(ws, payload) {
|
|
344
|
+
return sendWsFrame(ws, {
|
|
345
|
+
send: true,
|
|
346
|
+
serialized: JSON.stringify(payload)
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
function createRelay({ argv = process.argv.slice(2), env = process.env, stdin = process.stdin, stdout = process.stdout, stderr = process.stderr } = {}) {
|
|
350
|
+
const portPlan = resolvePortPlan(argv, env, readConfiguredPort(env));
|
|
351
|
+
const docStore = createDocStore();
|
|
352
|
+
let parserState = createParserState();
|
|
353
|
+
let ws = null;
|
|
354
|
+
let retryTimer = null;
|
|
355
|
+
let stableConnectionTimer = null;
|
|
356
|
+
let retryMs = INITIAL_RETRY_MS;
|
|
357
|
+
let nextPortIndex = 0;
|
|
358
|
+
let isStopping = false;
|
|
359
|
+
const pendingCodeActionById = /* @__PURE__ */ new Map();
|
|
360
|
+
const applyEditDaemonIdByEditorId = /* @__PURE__ */ new Map();
|
|
361
|
+
const unsyncedUris = /* @__PURE__ */ new Set();
|
|
362
|
+
const pendingForwards = [];
|
|
363
|
+
let nextEditorRequestId = 1;
|
|
364
|
+
function note(message) {
|
|
365
|
+
stderr.write(`[visions-relay] ${message}\n`);
|
|
366
|
+
}
|
|
367
|
+
function writeEditorMessage(message) {
|
|
368
|
+
stdout.write(serializeFrame(message));
|
|
369
|
+
}
|
|
370
|
+
function currentPort() {
|
|
371
|
+
const port = portPlan.ports[nextPortIndex % portPlan.ports.length];
|
|
372
|
+
nextPortIndex++;
|
|
373
|
+
return port;
|
|
374
|
+
}
|
|
375
|
+
function clearRetryTimer() {
|
|
376
|
+
if (!retryTimer) return;
|
|
377
|
+
clearTimeout(retryTimer);
|
|
378
|
+
retryTimer = null;
|
|
379
|
+
}
|
|
380
|
+
function clearStableConnectionTimer() {
|
|
381
|
+
if (!stableConnectionTimer) return;
|
|
382
|
+
clearTimeout(stableConnectionTimer);
|
|
383
|
+
stableConnectionTimer = null;
|
|
384
|
+
}
|
|
385
|
+
function scheduleReconnect(port) {
|
|
386
|
+
if (isStopping || retryTimer) return;
|
|
387
|
+
note(`lost the daemon on port ${port}; reconnecting in ${retryMs}ms`);
|
|
388
|
+
retryTimer = setTimeout(() => {
|
|
389
|
+
retryTimer = null;
|
|
390
|
+
connect();
|
|
391
|
+
}, retryMs);
|
|
392
|
+
retryTimer.unref?.();
|
|
393
|
+
retryMs = nextDelayMs(retryMs);
|
|
394
|
+
}
|
|
395
|
+
function forwardNotification(method, params) {
|
|
396
|
+
const message = daemonMessage(method, params);
|
|
397
|
+
if (sendWsJson(ws, message)) return true;
|
|
398
|
+
pendingForwards.push(message);
|
|
399
|
+
if (pendingForwards.length > MAX_PENDING_FORWARDS) pendingForwards.shift();
|
|
400
|
+
return false;
|
|
401
|
+
}
|
|
402
|
+
function flushPendingForwards() {
|
|
403
|
+
let sent = 0;
|
|
404
|
+
while (pendingForwards.length > 0) {
|
|
405
|
+
if (!sendWsJson(ws, pendingForwards[0])) return sent;
|
|
406
|
+
pendingForwards.shift();
|
|
407
|
+
sent += 1;
|
|
408
|
+
}
|
|
409
|
+
return sent;
|
|
410
|
+
}
|
|
411
|
+
function replayMirror(socket) {
|
|
412
|
+
const replay = planMirrorReplay(listDocs(docStore));
|
|
413
|
+
for (const frame of replay.frames) {
|
|
414
|
+
socket.send(frame.serialized);
|
|
415
|
+
unsyncedUris.delete(frame.uri);
|
|
416
|
+
}
|
|
417
|
+
for (const skipped of replay.skipped) {
|
|
418
|
+
sendWsJson(socket, daemonMessage("textDocument/didClose", { textDocument: { uri: skipped.uri } }));
|
|
419
|
+
if (!unsyncedUris.has(skipped.uri)) note(`mirror unsynced uri=${skipped.uri}: ${skipped.frameBytes} bytes exceeds ${MAX_DAEMON_FRAME_BYTES}`);
|
|
420
|
+
unsyncedUris.add(skipped.uri);
|
|
421
|
+
}
|
|
422
|
+
return replay;
|
|
423
|
+
}
|
|
424
|
+
function connect() {
|
|
425
|
+
if (isStopping) return;
|
|
426
|
+
const port = currentPort();
|
|
427
|
+
const socket = new WebSocket(`ws://127.0.0.1:${port}/visions`);
|
|
428
|
+
ws = socket;
|
|
429
|
+
socket.on("open", () => {
|
|
430
|
+
if (ws !== socket) return;
|
|
431
|
+
clearStableConnectionTimer();
|
|
432
|
+
stableConnectionTimer = setTimeout(() => {
|
|
433
|
+
if (ws !== socket) return;
|
|
434
|
+
retryMs = INITIAL_RETRY_MS;
|
|
435
|
+
stableConnectionTimer = null;
|
|
436
|
+
}, STABLE_CONNECTION_MS);
|
|
437
|
+
stableConnectionTimer.unref?.();
|
|
438
|
+
const replay = replayMirror(socket);
|
|
439
|
+
const forwarded = flushPendingForwards();
|
|
440
|
+
note(`connected to the daemon on port ${port} (replayed ${replay.frames.length} mirrored documents, skipped ${replay.skipped.length}, ${forwarded} held markers)`);
|
|
441
|
+
});
|
|
442
|
+
socket.on("message", (data, isBinary) => {
|
|
443
|
+
if (isBinary) return;
|
|
444
|
+
handleDaemonText(data.toString("utf8"));
|
|
445
|
+
});
|
|
446
|
+
socket.on("error", () => {});
|
|
447
|
+
socket.on("close", () => {
|
|
448
|
+
if (ws === socket) ws = null;
|
|
449
|
+
clearStableConnectionTimer();
|
|
450
|
+
failPendingCodeActions();
|
|
451
|
+
applyEditDaemonIdByEditorId.clear();
|
|
452
|
+
scheduleReconnect(port);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
function settleCodeAction(id, result) {
|
|
456
|
+
if (!pendingCodeActionById.has(id)) return false;
|
|
457
|
+
const timer = pendingCodeActionById.get(id);
|
|
458
|
+
pendingCodeActionById.delete(id);
|
|
459
|
+
if (timer) clearTimeout(timer);
|
|
460
|
+
writeEditorMessage(responseMessage(id, result));
|
|
461
|
+
return true;
|
|
462
|
+
}
|
|
463
|
+
function failPendingCodeActions() {
|
|
464
|
+
for (const id of [...pendingCodeActionById.keys()]) settleCodeAction(id, null);
|
|
465
|
+
}
|
|
466
|
+
function forwardCodeAction(id, params) {
|
|
467
|
+
if (!sendWsJson(ws, daemonRequest(id, "textDocument/codeAction", params))) {
|
|
468
|
+
writeEditorMessage(responseMessage(id, null));
|
|
469
|
+
return;
|
|
470
|
+
}
|
|
471
|
+
const timer = setTimeout(() => settleCodeAction(id, null), CODE_ACTION_TIMEOUT_MS);
|
|
472
|
+
timer.unref?.();
|
|
473
|
+
pendingCodeActionById.set(id, timer);
|
|
474
|
+
}
|
|
475
|
+
function forwardApplyEdit(daemonId, params) {
|
|
476
|
+
const editorId = `glimmervoid-visions-${nextEditorRequestId}`;
|
|
477
|
+
nextEditorRequestId += 1;
|
|
478
|
+
applyEditDaemonIdByEditorId.set(editorId, daemonId);
|
|
479
|
+
writeEditorMessage(editorRequest(editorId, APPLY_EDIT_METHOD, params));
|
|
480
|
+
}
|
|
481
|
+
function handleEditorResponse(editorId, msg) {
|
|
482
|
+
if (typeof editorId !== "string" || !applyEditDaemonIdByEditorId.has(editorId)) return;
|
|
483
|
+
const daemonId = applyEditDaemonIdByEditorId.get(editorId);
|
|
484
|
+
applyEditDaemonIdByEditorId.delete(editorId);
|
|
485
|
+
const refused = "error" in msg ? msg.error : void 0;
|
|
486
|
+
const result = "result" in msg ? msg.result : void 0;
|
|
487
|
+
sendWsJson(ws, daemonResponse(daemonId, refused ? { applied: false } : result));
|
|
488
|
+
}
|
|
489
|
+
function stop(exitCode = 0) {
|
|
490
|
+
if (isStopping) return;
|
|
491
|
+
isStopping = true;
|
|
492
|
+
note(`shutting down (exit ${exitCode})`);
|
|
493
|
+
clearRetryTimer();
|
|
494
|
+
clearStableConnectionTimer();
|
|
495
|
+
stdin.pause();
|
|
496
|
+
if (ws) ws.close();
|
|
497
|
+
setImmediate(() => process.exit(exitCode));
|
|
498
|
+
}
|
|
499
|
+
function handleDaemonText(text) {
|
|
500
|
+
let msg = null;
|
|
501
|
+
try {
|
|
502
|
+
msg = JSON.parse(text);
|
|
503
|
+
} catch {
|
|
504
|
+
return;
|
|
505
|
+
}
|
|
506
|
+
if (!msg || typeof msg !== "object") return;
|
|
507
|
+
if (msg.type === "publishDiagnostics") {
|
|
508
|
+
writeEditorMessage(editorNotification("textDocument/publishDiagnostics", msg.params));
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
if (msg.type === "lsp-response") {
|
|
512
|
+
settleCodeAction(msg.id, msg.result);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
if (msg.type === "lsp-request" && msg.method === "workspace/applyEdit") forwardApplyEdit(msg.id, msg.params);
|
|
516
|
+
}
|
|
517
|
+
function updateMirror(method, params) {
|
|
518
|
+
const documentParams = asDocumentParams(params);
|
|
519
|
+
if (method === "textDocument/didOpen") return applyDidOpen(docStore, documentParams);
|
|
520
|
+
if (method === "textDocument/didChange") return applyDidChange(docStore, documentParams);
|
|
521
|
+
if (method === "textDocument/didClose") return applyDidClose(docStore, documentParams);
|
|
522
|
+
return {
|
|
523
|
+
applied: false,
|
|
524
|
+
reason: "not-buffer-method"
|
|
525
|
+
};
|
|
526
|
+
}
|
|
527
|
+
function sendMirrorNotification(method, params, mirrorUpdate) {
|
|
528
|
+
const uri = uriOfParams(asDocumentParams(params));
|
|
529
|
+
const originalMessage = daemonMessage(method, params);
|
|
530
|
+
if (!uri) return sendWsJson(ws, originalMessage);
|
|
531
|
+
const isUnsynced = unsyncedUris.has(uri);
|
|
532
|
+
if (!mirrorUpdate.applied && !isUnsynced) return sendWsJson(ws, originalMessage);
|
|
533
|
+
if (!mirrorUpdate.applied && method === "textDocument/didClose") {
|
|
534
|
+
unsyncedUris.delete(uri);
|
|
535
|
+
return false;
|
|
536
|
+
}
|
|
537
|
+
if (!mirrorUpdate.applied) return false;
|
|
538
|
+
const originalFrame = decideDaemonFrame(originalMessage);
|
|
539
|
+
const doc = method !== "textDocument/didClose" && (isUnsynced || !originalFrame.send) ? getDoc(docStore, uri) : null;
|
|
540
|
+
const fullOpenMessage = doc ? replayDidOpenMessage(doc) : null;
|
|
541
|
+
const fullOpenFrame = fullOpenMessage ? decideDaemonFrame(fullOpenMessage) : null;
|
|
542
|
+
const syncPlan = decideMirrorSync({
|
|
543
|
+
method,
|
|
544
|
+
isUnsynced,
|
|
545
|
+
originalFrameFits: originalFrame.send,
|
|
546
|
+
fullFrameFits: fullOpenFrame?.send === true
|
|
547
|
+
});
|
|
548
|
+
if (syncPlan.markUnsynced) unsyncedUris.add(uri);
|
|
549
|
+
if (syncPlan.shouldLog) note(`mirror unsynced uri=${uri}: ${originalFrame.frameBytes} bytes exceeds ${MAX_DAEMON_FRAME_BYTES}`);
|
|
550
|
+
let didSend = false;
|
|
551
|
+
for (const action of syncPlan.actions) {
|
|
552
|
+
if (action === "original") didSend = sendWsFrame(ws, originalFrame) || didSend;
|
|
553
|
+
if (action === "close") {
|
|
554
|
+
const closeMessage = daemonMessage("textDocument/didClose", { textDocument: { uri } });
|
|
555
|
+
didSend = sendWsJson(ws, closeMessage) || didSend;
|
|
556
|
+
}
|
|
557
|
+
if (action === "full-open" && fullOpenFrame && sendWsFrame(ws, fullOpenFrame)) {
|
|
558
|
+
unsyncedUris.delete(uri);
|
|
559
|
+
didSend = true;
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
if (syncPlan.forgetUnsynced) unsyncedUris.delete(uri);
|
|
563
|
+
return didSend;
|
|
564
|
+
}
|
|
565
|
+
function handleNotification(method, params) {
|
|
566
|
+
if (MIRROR_METHODS.has(method)) {
|
|
567
|
+
const mirrorUpdate = updateMirror(method, params);
|
|
568
|
+
if (!mirrorUpdate.applied) note(`mirror update failed method=${method} uri=${uriOfParams(asDocumentParams(params)) || "<unknown>"} reason=${mirrorUpdate.reason}${mirrorFailureDetail(mirrorUpdate)}`);
|
|
569
|
+
return sendMirrorNotification(method, params, mirrorUpdate);
|
|
570
|
+
}
|
|
571
|
+
if (FORWARDED_METHODS.has(method)) return forwardNotification(method, params);
|
|
572
|
+
if (method === "exit") stop(0);
|
|
573
|
+
return false;
|
|
574
|
+
}
|
|
575
|
+
function handleRequest(id, method, params) {
|
|
576
|
+
if (method === "textDocument/codeAction") {
|
|
577
|
+
forwardCodeAction(id, params);
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
if (method === "initialize") {
|
|
581
|
+
note(`initialize answered: textDocumentSync change=2 (incremental)`);
|
|
582
|
+
writeEditorMessage(responseMessage(id, initializeResult()));
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
if (method === "shutdown") {
|
|
586
|
+
note("shutdown requested by the editor");
|
|
587
|
+
writeEditorMessage(responseMessage(id, null));
|
|
588
|
+
return;
|
|
589
|
+
}
|
|
590
|
+
writeEditorMessage(methodNotFoundResponse(id, method));
|
|
591
|
+
}
|
|
592
|
+
function handleEditorMessage(msg) {
|
|
593
|
+
const classification = classifyMessage(msg);
|
|
594
|
+
const params = "params" in msg ? msg.params : void 0;
|
|
595
|
+
if (classification.kind === "request") {
|
|
596
|
+
handleRequest(classification.id, classification.method, params);
|
|
597
|
+
return false;
|
|
598
|
+
}
|
|
599
|
+
if (classification.kind === "notification" && classification.method) return handleNotification(classification.method, params);
|
|
600
|
+
if (classification.kind === "response") handleEditorResponse(classification.id, msg);
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
function handleStdinChunk(chunk) {
|
|
604
|
+
const bytes = typeof chunk === "string" ? Buffer.from(chunk) : Buffer.from(chunk);
|
|
605
|
+
const parsed = feedFrameBytes(parserState, bytes);
|
|
606
|
+
parserState = parsed.state;
|
|
607
|
+
for (const msg of parsed.messages) handleEditorMessage(msg);
|
|
608
|
+
}
|
|
609
|
+
function start() {
|
|
610
|
+
connect();
|
|
611
|
+
stdin.on("data", handleStdinChunk);
|
|
612
|
+
stdin.on("end", () => stop(0));
|
|
613
|
+
}
|
|
614
|
+
return {
|
|
615
|
+
start,
|
|
616
|
+
stop,
|
|
617
|
+
handleEditorMessage,
|
|
618
|
+
handleDaemonText,
|
|
619
|
+
portPlan
|
|
620
|
+
};
|
|
621
|
+
}
|
|
622
|
+
function main() {
|
|
623
|
+
createRelay().start();
|
|
624
|
+
}
|
|
625
|
+
if (process.argv[1] === import.meta.filename) main();
|
|
626
|
+
//#endregion
|
|
627
|
+
export { ACTIVITY_METHOD, APPLY_EDIT_METHOD, CODE_ACTION_METHOD, CODE_ACTION_TIMEOUT_MS, MAX_DAEMON_FRAME_BYTES, STABLE_CONNECTION_MS, SYNC_KIND_INCREMENTAL, createRelay, daemonMessage, decideDaemonFrame, initializeResult, methodNotFoundResponse, nextDelayMs, planMirrorReplay, readConfiguredPort, replayDidOpenMessage, resolvePortPlan, sendWsFrame, sendWsJson };
|