@hydranium/core 1.0.0-next.252 → 1.0.0-next.254
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/lib/langium/model-service/model-service.js +2 -2
- package/lib/langium/model-service/model-service.js.map +1 -1
- package/lib/lsp/connection-features.d.ts +1 -1
- package/lib/lsp/connection-features.js +3 -3
- package/lib/lsp/connection-features.js.map +1 -1
- package/lib/lsp/diagnostics-connection.js +3 -3
- package/lib/lsp/diagnostics-connection.js.map +1 -1
- package/lib/testing/node/applied-edit-capture.d.ts +29 -0
- package/lib/testing/node/applied-edit-capture.d.ts.map +1 -0
- package/lib/testing/node/applied-edit-capture.js +115 -0
- package/lib/testing/node/applied-edit-capture.js.map +1 -0
- package/lib/testing/node/lsp-server-connection.d.ts +1 -1
- package/lib/testing/node/lsp-server-connection.d.ts.map +1 -1
- package/lib/testing/node/lsp-server-connection.js +7 -101
- package/lib/testing/node/lsp-server-connection.js.map +1 -1
- package/lib/testing/node/spawned-server.d.ts +16 -1
- package/lib/testing/node/spawned-server.d.ts.map +1 -1
- package/lib/testing/node/spawned-server.js +5 -0
- package/lib/testing/node/spawned-server.js.map +1 -1
- package/lib/util/connection-liveness.d.ts +12 -0
- package/lib/util/connection-liveness.d.ts.map +1 -1
- package/lib/util/connection-liveness.js +24 -0
- package/lib/util/connection-liveness.js.map +1 -1
- package/package.json +5 -5
- package/src/langium/model-service/model-service.ts +2 -2
- package/src/lsp/connection-features.ts +3 -3
- package/src/lsp/diagnostics-connection.ts +3 -3
- package/src/testing/node/applied-edit-capture.ts +139 -0
- package/src/testing/node/lsp-server-connection.ts +8 -105
- package/src/testing/node/spawned-server.ts +21 -1
- package/src/util/connection-liveness.ts +27 -0
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawned-server.d.ts","sourceRoot":"","sources":["../../../src/testing/node/spawned-server.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH,OAAO,EAAE,KAAK,OAAO,EAAE,MAAM,6BAA6B,CAAC;AAK3D,OAAO,EAQJ,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,KAAK,kBAAkB,EACvB,KAAK,wBAAwB,EAC/B,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"spawned-server.d.ts","sourceRoot":"","sources":["../../../src/testing/node/spawned-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -11,6 +11,7 @@ import { existsSync } from 'node:fs';
|
|
|
11
11
|
import * as path from 'node:path';
|
|
12
12
|
import { pathToFileURL } from 'node:url';
|
|
13
13
|
import { ExitNotification, InitializeRequest, InitializedNotification, LogMessageNotification, ShutdownRequest, PublishDiagnosticsNotification, createProtocolConnection } from 'vscode-languageserver-protocol/node';
|
|
14
|
+
import { captureAppliedEdits } from './applied-edit-capture.js';
|
|
14
15
|
/**
|
|
15
16
|
* Default bound on the `initialize` handshake, and the reason there is one at
|
|
16
17
|
* all: without it a child that never answers hangs until the test runner's own
|
|
@@ -137,6 +138,7 @@ export async function startSpawnedServer(options) {
|
|
|
137
138
|
connection.onNotification(LogMessageNotification.type, params => {
|
|
138
139
|
logMessages.push(params);
|
|
139
140
|
});
|
|
141
|
+
const appliedEditCapture = captureAppliedEdits(connection, RESPONSE_TIMEOUT_MS);
|
|
140
142
|
connection.listen();
|
|
141
143
|
const killTimeoutMs = options.killTimeoutMs ?? DEFAULT_KILL_TIMEOUT_MS;
|
|
142
144
|
/** Terminate unconditionally and wait for the child to be reaped. */
|
|
@@ -285,7 +287,10 @@ export async function startSpawnedServer(options) {
|
|
|
285
287
|
waiters.add(notify);
|
|
286
288
|
});
|
|
287
289
|
},
|
|
290
|
+
appliedEdits: appliedEditCapture.appliedEdits,
|
|
291
|
+
nextAppliedEdit: appliedEditCapture.nextAppliedEdit,
|
|
288
292
|
dispose() {
|
|
293
|
+
appliedEditCapture.dispose();
|
|
289
294
|
liveServers.delete(server);
|
|
290
295
|
disposal ??= terminate();
|
|
291
296
|
return disposal;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"spawned-server.js","sourceRoot":"","sources":["../../../src/testing/node/spawned-server.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAqClF,OAAO,EAAuC,KAAK,EAAE,MAAM,oBAAoB,CAAC;AAChF,OAAO,EAAE,UAAU,EAAE,MAAM,SAAS,CAAC;AACrC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAC;AAClC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EACJ,gBAAgB,EAChB,iBAAiB,EACjB,uBAAuB,EACvB,sBAAsB,EACtB,eAAe,EACf,8BAA8B,EAC9B,wBAAwB,EAO1B,MAAM,qCAAqC,CAAC;
|
|
1
|
+
{"version":3,"file":"spawned-server.js","sourceRoot":"","sources":["../../../src/testing/node/spawned-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -23,4 +23,16 @@
|
|
|
23
23
|
* that, since a logger has nowhere to report a logging failure anyway.
|
|
24
24
|
*/
|
|
25
25
|
export declare function isConnectionGoneError(err: unknown): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Whether `err` means the client a connection serves has gone: what
|
|
28
|
+
* {@link isConnectionGoneError} recognises, or a write to a socket the client
|
|
29
|
+
* closed (`EPIPE`, `ECONNRESET`), matched by code or, for a request, by the
|
|
30
|
+
* code's name in the message.
|
|
31
|
+
*
|
|
32
|
+
* Only for a call whose errors can come from that connection alone. A socket
|
|
33
|
+
* error names no peer, so where a file system or another socket can fail the
|
|
34
|
+
* same call, it is a genuine failure and {@link isConnectionGoneError} is the
|
|
35
|
+
* one to ask.
|
|
36
|
+
*/
|
|
37
|
+
export declare function isClientGoneError(err: unknown): boolean;
|
|
26
38
|
//# sourceMappingURL=connection-liveness.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-liveness.d.ts","sourceRoot":"","sources":["../../src/util/connection-liveness.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAuClF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAO3D"}
|
|
1
|
+
{"version":3,"file":"connection-liveness.d.ts","sourceRoot":"","sources":["../../src/util/connection-liveness.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAuClF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,qBAAqB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CAO3D;AAOD;;;;;;;;;;GAUG;AACH,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,OAAO,GAAG,OAAO,CASvD"}
|
|
@@ -64,4 +64,28 @@ export function isConnectionGoneError(err) {
|
|
|
64
64
|
const normalized = message.toLowerCase();
|
|
65
65
|
return CONNECTION_GONE_PATTERNS.some(pattern => pattern.test(normalized));
|
|
66
66
|
}
|
|
67
|
+
const SOCKET_GONE_CODES = new Set(['EPIPE', 'ECONNRESET']);
|
|
68
|
+
/** `write EPIPE` / `read ECONNRESET`, as a request's `ResponseError` copies the message. */
|
|
69
|
+
const SOCKET_GONE_PATTERN = /\b(epipe|econnreset)\b/;
|
|
70
|
+
/**
|
|
71
|
+
* Whether `err` means the client a connection serves has gone: what
|
|
72
|
+
* {@link isConnectionGoneError} recognises, or a write to a socket the client
|
|
73
|
+
* closed (`EPIPE`, `ECONNRESET`), matched by code or, for a request, by the
|
|
74
|
+
* code's name in the message.
|
|
75
|
+
*
|
|
76
|
+
* Only for a call whose errors can come from that connection alone. A socket
|
|
77
|
+
* error names no peer, so where a file system or another socket can fail the
|
|
78
|
+
* same call, it is a genuine failure and {@link isConnectionGoneError} is the
|
|
79
|
+
* one to ask.
|
|
80
|
+
*/
|
|
81
|
+
export function isClientGoneError(err) {
|
|
82
|
+
if (isConnectionGoneError(err)) {
|
|
83
|
+
return true;
|
|
84
|
+
}
|
|
85
|
+
if (err instanceof Error && 'code' in err && typeof err.code === 'string' && SOCKET_GONE_CODES.has(err.code)) {
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
89
|
+
return SOCKET_GONE_PATTERN.test(message.toLowerCase());
|
|
90
|
+
}
|
|
67
91
|
//# sourceMappingURL=connection-liveness.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"connection-liveness.js","sourceRoot":"","sources":["../../src/util/connection-liveness.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,wBAAwB,GAAG;IAC9B,wBAAwB;IACxB,sBAAsB;IACtB,yBAAyB;IACzB,uBAAuB;IACvB,8BAA8B;CAChC,CAAC;AAEF,MAAM,qBAAqB,GAAG,sBAAsB,CAAC;AAErD,SAAS,sBAAsB,CAAC,GAAY;IACzC,OAAO,GAAG,YAAY,KAAK,IAAI,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,qBAAqB,CAAC;AACtF,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAY;IAC/C,IAAI,sBAAsB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC;IACf,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACzC,OAAO,wBAAwB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;AAC7E,CAAC"}
|
|
1
|
+
{"version":3,"file":"connection-liveness.js","sourceRoot":"","sources":["../../src/util/connection-liveness.ts"],"names":[],"mappings":"AAAA;;;;;;;kFAOkF;AAElF;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,MAAM,wBAAwB,GAAG;IAC9B,wBAAwB;IACxB,sBAAsB;IACtB,yBAAyB;IACzB,uBAAuB;IACvB,8BAA8B;CAChC,CAAC;AAEF,MAAM,qBAAqB,GAAG,sBAAsB,CAAC;AAErD,SAAS,sBAAsB,CAAC,GAAY;IACzC,OAAO,GAAG,YAAY,KAAK,IAAI,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,IAAI,KAAK,qBAAqB,CAAC;AACtF,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,qBAAqB,CAAC,GAAY;IAC/C,IAAI,sBAAsB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC;IACf,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,MAAM,UAAU,GAAG,OAAO,CAAC,WAAW,EAAE,CAAC;IACzC,OAAO,wBAAwB,CAAC,IAAI,CAAC,OAAO,CAAC,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,iBAAiB,GAAG,IAAI,GAAG,CAAC,CAAC,OAAO,EAAE,YAAY,CAAC,CAAC,CAAC;AAE3D,4FAA4F;AAC5F,MAAM,mBAAmB,GAAG,wBAAwB,CAAC;AAErD;;;;;;;;;;GAUG;AACH,MAAM,UAAU,iBAAiB,CAAC,GAAY;IAC3C,IAAI,qBAAqB,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,OAAO,IAAI,CAAC;IACf,CAAC;IACD,IAAI,GAAG,YAAY,KAAK,IAAI,MAAM,IAAI,GAAG,IAAI,OAAO,GAAG,CAAC,IAAI,KAAK,QAAQ,IAAI,iBAAiB,CAAC,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC;QAC5G,OAAO,IAAI,CAAC;IACf,CAAC;IACD,MAAM,OAAO,GAAG,GAAG,YAAY,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO,mBAAmB,CAAC,IAAI,CAAC,OAAO,CAAC,WAAW,EAAE,CAAC,CAAC;AAC1D,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hydranium/core",
|
|
3
|
-
"version": "1.0.0-next.
|
|
3
|
+
"version": "1.0.0-next.254",
|
|
4
4
|
"description": "Foundational runtime of the hydranium framework: AST coordination layer (services, integrity, AST extension, serialization, multi-client documents) plus the LSP textual head at the /lsp subpath. Consumed by protocol-head packages (@hydranium/data-server, @hydranium/glsp-server).",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ast",
|
|
@@ -83,8 +83,8 @@
|
|
|
83
83
|
"diff": "^5.2.0"
|
|
84
84
|
},
|
|
85
85
|
"devDependencies": {
|
|
86
|
-
"@hydranium/langium": "1.0.0-next.
|
|
87
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
86
|
+
"@hydranium/langium": "1.0.0-next.254",
|
|
87
|
+
"@hydranium/protocol": "1.0.0-next.254",
|
|
88
88
|
"@playwright/test": "^1.40.0",
|
|
89
89
|
"@types/diff": "^5.2.0",
|
|
90
90
|
"rimraf": "^5.0.0",
|
|
@@ -96,8 +96,8 @@
|
|
|
96
96
|
"vscode-languageserver-types": "^3.17.5"
|
|
97
97
|
},
|
|
98
98
|
"peerDependencies": {
|
|
99
|
-
"@hydranium/langium": "1.0.0-next.
|
|
100
|
-
"@hydranium/protocol": "1.0.0-next.
|
|
99
|
+
"@hydranium/langium": "1.0.0-next.254",
|
|
100
|
+
"@hydranium/protocol": "1.0.0-next.254",
|
|
101
101
|
"@playwright/test": "^1.40.0",
|
|
102
102
|
"vscode-jsonrpc": "^9.0.0",
|
|
103
103
|
"vscode-languageserver": "~10.0.1",
|
|
@@ -26,7 +26,7 @@ import { type DocumentUriPolicy } from '../workspace/document-uri-policy.js';
|
|
|
26
26
|
import { ReentrantWriteLockError, isInsideWriteLock, isWriteLockScopeInstalled } from '../workspace/write-lock-scope.js';
|
|
27
27
|
import { CancellationToken, Disposable } from 'vscode-languageserver';
|
|
28
28
|
import { AstDocument } from '../../documents/ast-document-manager.js';
|
|
29
|
-
import {
|
|
29
|
+
import { isClientGoneError } from '../../util/connection-liveness.js';
|
|
30
30
|
import { type LogNameOptions } from '../diagnostics/logger.js';
|
|
31
31
|
import { IntegrityService } from '../integrity/integrity-service.js';
|
|
32
32
|
import { labelPhaseListener } from '../document-builder/labeled-phase-listener.js';
|
|
@@ -1120,7 +1120,7 @@ export class DefaultModelService<
|
|
|
1120
1120
|
// ordinary shutdown. Reporting it at `error` makes routine teardown
|
|
1121
1121
|
// look like it needs investigating. A genuine applyEdit failure
|
|
1122
1122
|
// (client refused, request malformed) still surfaces at `error`.
|
|
1123
|
-
if (
|
|
1123
|
+
if (isClientGoneError(err)) {
|
|
1124
1124
|
uriLogger.debug(`applyEdit to ${LANGUAGE_CLIENT_ID} skipped: client disconnected`);
|
|
1125
1125
|
} else {
|
|
1126
1126
|
uriLogger.error(`applyEdit to ${LANGUAGE_CLIENT_ID} failed: ${err}`);
|
|
@@ -8,14 +8,14 @@
|
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
10
|
import { LogMessageNotification, MessageType, type Features, type RemoteConsole } from 'vscode-languageserver';
|
|
11
|
-
import {
|
|
11
|
+
import { isClientGoneError } from '../util/connection-liveness.js';
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Add Hydranium's connection-liveness handling to a language-server
|
|
15
15
|
* connection's console. The upstream `RemoteConsole` catches every failed log
|
|
16
16
|
* notification and prints `Sending log message failed`, so every ordinary
|
|
17
17
|
* teardown reports a failure; this console drops only a failure that
|
|
18
|
-
* {@link
|
|
18
|
+
* {@link isClientGoneError} recognises and re-raises any other as an
|
|
19
19
|
* unhandled rejection.
|
|
20
20
|
*
|
|
21
21
|
* Supply it where the connection is created. `createConnection` builds the
|
|
@@ -58,7 +58,7 @@ export function withHydraniumLspFeatures<T extends Features>(features: T): T {
|
|
|
58
58
|
}
|
|
59
59
|
|
|
60
60
|
private handleLogSendFailure(error: unknown): void {
|
|
61
|
-
if (!
|
|
61
|
+
if (!isClientGoneError(error)) {
|
|
62
62
|
void Promise.reject(error);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
********************************************************************************/
|
|
9
9
|
|
|
10
10
|
import type { Connection } from 'vscode-languageserver';
|
|
11
|
-
import {
|
|
11
|
+
import { isClientGoneError } from '../util/connection-liveness.js';
|
|
12
12
|
|
|
13
13
|
const wrappedConnections = new WeakSet<object>();
|
|
14
14
|
|
|
@@ -40,13 +40,13 @@ export function guardDiagnosticsConnection(connection: Connection): void {
|
|
|
40
40
|
try {
|
|
41
41
|
result = sendDiagnostics(params);
|
|
42
42
|
} catch (error: unknown) {
|
|
43
|
-
if (
|
|
43
|
+
if (isClientGoneError(error)) {
|
|
44
44
|
return Promise.resolve();
|
|
45
45
|
}
|
|
46
46
|
throw error;
|
|
47
47
|
}
|
|
48
48
|
void result.catch(error => {
|
|
49
|
-
if (
|
|
49
|
+
if (isClientGoneError(error)) {
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
throw error;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/********************************************************************************
|
|
2
|
+
* Copyright (c) 2026 CrossBreeze, EclipseSource and others.
|
|
3
|
+
*
|
|
4
|
+
* This program and the accompanying materials are made available under the
|
|
5
|
+
* terms of the MIT License which is available in the project root.
|
|
6
|
+
*
|
|
7
|
+
* SPDX-License-Identifier: MIT
|
|
8
|
+
********************************************************************************/
|
|
9
|
+
|
|
10
|
+
import {
|
|
11
|
+
ApplyWorkspaceEditRequest,
|
|
12
|
+
type ApplyWorkspaceEditParams,
|
|
13
|
+
type ApplyWorkspaceEditResult,
|
|
14
|
+
type ProtocolConnection,
|
|
15
|
+
type WorkspaceEdit
|
|
16
|
+
} from 'vscode-languageserver-protocol/node';
|
|
17
|
+
import type { AppliedEdit, NextAppliedEditOptions } from './lsp-server-connection.js';
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* The client side of `workspace/applyEdit` on one LSP connection, shared by the
|
|
21
|
+
* in-process and the spawned tiers so their capture and wait cannot drift.
|
|
22
|
+
*/
|
|
23
|
+
export interface AppliedEditCapture {
|
|
24
|
+
readonly appliedEdits: ReadonlyArray<AppliedEdit>;
|
|
25
|
+
nextAppliedEdit(uri: string, timeoutMsOrOptions?: number | NextAppliedEditOptions): Promise<AppliedEdit>;
|
|
26
|
+
setApplyEditHandler(handler: (params: ApplyWorkspaceEditParams) => ApplyWorkspaceEditResult): void;
|
|
27
|
+
dispose(): void;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Answer and record every `workspace/applyEdit` arriving on `client`.
|
|
32
|
+
*
|
|
33
|
+
* Register before the connection can receive one: `vscode-jsonrpc` keeps a
|
|
34
|
+
* single handler per request type, so this must be the only `applyEdit`
|
|
35
|
+
* handler on `client`, and a second registration displaces the capture.
|
|
36
|
+
*/
|
|
37
|
+
export function captureAppliedEdits(client: ProtocolConnection, defaultTimeoutMs: number): AppliedEditCapture {
|
|
38
|
+
const appliedEdits: AppliedEdit[] = [];
|
|
39
|
+
const waiters: Array<(edit: AppliedEdit) => boolean> = [];
|
|
40
|
+
let handler: (params: ApplyWorkspaceEditParams) => ApplyWorkspaceEditResult = () => ({ applied: true });
|
|
41
|
+
const subscription = client.onRequest(ApplyWorkspaceEditRequest.type, (params: ApplyWorkspaceEditParams) => {
|
|
42
|
+
const edit: AppliedEdit = { params, uris: editedUris(params.edit), text: insertedText(params.edit) };
|
|
43
|
+
// Record before the handler runs, so a handler that rejects the edit or
|
|
44
|
+
// throws still leaves the push observable — the send is what the egress
|
|
45
|
+
// assertion is about.
|
|
46
|
+
appliedEdits.push(edit);
|
|
47
|
+
for (const waiter of [...waiters]) {
|
|
48
|
+
if (waiter(edit)) {
|
|
49
|
+
const at = waiters.indexOf(waiter);
|
|
50
|
+
if (at >= 0) {
|
|
51
|
+
waiters.splice(at, 1);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
return handler(params);
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
return {
|
|
59
|
+
appliedEdits,
|
|
60
|
+
|
|
61
|
+
nextAppliedEdit(uri: string, timeoutMsOrOptions?: number | NextAppliedEditOptions): Promise<AppliedEdit> {
|
|
62
|
+
const options: NextAppliedEditOptions =
|
|
63
|
+
typeof timeoutMsOrOptions === 'number' ? { timeoutMs: timeoutMsOrOptions } : (timeoutMsOrOptions ?? {});
|
|
64
|
+
const timeoutMs = options.timeoutMs ?? defaultTimeoutMs;
|
|
65
|
+
const { fromIndex, match } = options;
|
|
66
|
+
const matches = (edit: AppliedEdit): boolean => edit.uris.includes(uri) && (match?.(edit) ?? true);
|
|
67
|
+
if (fromIndex !== undefined) {
|
|
68
|
+
// Replay from the capture first: the push may already have arrived,
|
|
69
|
+
// and a waiter only ever sees later ones.
|
|
70
|
+
const captured = appliedEdits.slice(Math.max(0, fromIndex)).find(matches);
|
|
71
|
+
if (captured) {
|
|
72
|
+
return Promise.resolve(captured);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return new Promise<AppliedEdit>((resolve, reject) => {
|
|
76
|
+
const waiter = (edit: AppliedEdit): boolean => {
|
|
77
|
+
if (!matches(edit)) {
|
|
78
|
+
return false;
|
|
79
|
+
}
|
|
80
|
+
clearTimeout(timer);
|
|
81
|
+
resolve(edit);
|
|
82
|
+
return true;
|
|
83
|
+
};
|
|
84
|
+
const timer = setTimeout(() => {
|
|
85
|
+
const at = waiters.indexOf(waiter);
|
|
86
|
+
if (at >= 0) {
|
|
87
|
+
waiters.splice(at, 1);
|
|
88
|
+
}
|
|
89
|
+
reject(new Error(`Timed out waiting for a workspace/applyEdit addressing ${uri}`));
|
|
90
|
+
}, timeoutMs);
|
|
91
|
+
waiters.push(waiter);
|
|
92
|
+
});
|
|
93
|
+
},
|
|
94
|
+
|
|
95
|
+
setApplyEditHandler(next: (params: ApplyWorkspaceEditParams) => ApplyWorkspaceEditResult): void {
|
|
96
|
+
handler = next;
|
|
97
|
+
},
|
|
98
|
+
|
|
99
|
+
dispose(): void {
|
|
100
|
+
subscription.dispose();
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** Every URI a {@link WorkspaceEdit} addresses, in `documentChanges` then `changes` order. */
|
|
106
|
+
function editedUris(edit: WorkspaceEdit): string[] {
|
|
107
|
+
const uris: string[] = [];
|
|
108
|
+
for (const change of edit.documentChanges ?? []) {
|
|
109
|
+
if ('textDocument' in change) {
|
|
110
|
+
uris.push(change.textDocument.uri);
|
|
111
|
+
} else if ('oldUri' in change) {
|
|
112
|
+
uris.push(change.oldUri, change.newUri);
|
|
113
|
+
} else {
|
|
114
|
+
uris.push(change.uri);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
uris.push(...Object.keys(edit.changes ?? {}));
|
|
118
|
+
return uris;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/** Every text edit's `newText` in a {@link WorkspaceEdit}, concatenated in edit order. */
|
|
122
|
+
function insertedText(edit: WorkspaceEdit): string {
|
|
123
|
+
const parts: string[] = [];
|
|
124
|
+
for (const change of edit.documentChanges ?? []) {
|
|
125
|
+
if ('edits' in change) {
|
|
126
|
+
for (const textEdit of change.edits) {
|
|
127
|
+
if ('newText' in textEdit) {
|
|
128
|
+
parts.push(textEdit.newText);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
for (const textEdits of Object.values(edit.changes ?? {})) {
|
|
134
|
+
for (const textEdit of textEdits) {
|
|
135
|
+
parts.push(textEdit.newText);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return parts.join('');
|
|
139
|
+
}
|
|
@@ -12,6 +12,7 @@ import { PassThrough, type TransformCallback } from 'node:stream';
|
|
|
12
12
|
import { StreamMessageReader, StreamMessageWriter } from 'vscode-jsonrpc/node';
|
|
13
13
|
import { type Connection, ProposedFeatures } from 'vscode-languageserver/node';
|
|
14
14
|
import { withHydraniumLspFeatures } from '../../lsp/connection-features.js';
|
|
15
|
+
import { captureAppliedEdits } from './applied-edit-capture.js';
|
|
15
16
|
// The node `createConnection` from `vscode-languageserver/node` is built for a
|
|
16
17
|
// standalone server PROCESS: it wires `process.exit()` onto the input stream's
|
|
17
18
|
// `end`/`close` events, onto the `exit` notification (via its watchDog), and onto
|
|
@@ -26,7 +27,6 @@ import { withHydraniumLspFeatures } from '../../lsp/connection-features.js';
|
|
|
26
27
|
// package map the LSP 3.18 libs ship.)
|
|
27
28
|
import { createConnection, type WatchDog } from 'vscode-languageserver';
|
|
28
29
|
import {
|
|
29
|
-
ApplyWorkspaceEditRequest,
|
|
30
30
|
CompletionRequest,
|
|
31
31
|
DidChangeTextDocumentNotification,
|
|
32
32
|
DidCloseTextDocumentNotification,
|
|
@@ -48,8 +48,7 @@ import {
|
|
|
48
48
|
type Position,
|
|
49
49
|
type ProtocolConnection,
|
|
50
50
|
type PublishDiagnosticsParams,
|
|
51
|
-
type SemanticTokens
|
|
52
|
-
type WorkspaceEdit
|
|
51
|
+
type SemanticTokens
|
|
53
52
|
} from 'vscode-languageserver-protocol/node';
|
|
54
53
|
|
|
55
54
|
/**
|
|
@@ -122,7 +121,7 @@ export interface NextAppliedEditOptions {
|
|
|
122
121
|
* and needed for the same reason: one write can fan out to several pushes.
|
|
123
122
|
*/
|
|
124
123
|
readonly fromIndex?: number;
|
|
125
|
-
/** Reject after this long. Defaults to the
|
|
124
|
+
/** Reject after this long. Defaults to the harness's own timeout, which differs by tier. */
|
|
126
125
|
readonly timeoutMs?: number;
|
|
127
126
|
/**
|
|
128
127
|
* Additional predicate the edit must satisfy. The framework coalesces
|
|
@@ -133,42 +132,6 @@ export interface NextAppliedEditOptions {
|
|
|
133
132
|
readonly match?: (edit: AppliedEdit) => boolean;
|
|
134
133
|
}
|
|
135
134
|
|
|
136
|
-
/** Every URI a {@link WorkspaceEdit} addresses, in `documentChanges` then `changes` order. */
|
|
137
|
-
function editedUris(edit: WorkspaceEdit): string[] {
|
|
138
|
-
const uris: string[] = [];
|
|
139
|
-
for (const change of edit.documentChanges ?? []) {
|
|
140
|
-
if ('textDocument' in change) {
|
|
141
|
-
uris.push(change.textDocument.uri);
|
|
142
|
-
} else if ('oldUri' in change) {
|
|
143
|
-
uris.push(change.oldUri, change.newUri);
|
|
144
|
-
} else {
|
|
145
|
-
uris.push(change.uri);
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
uris.push(...Object.keys(edit.changes ?? {}));
|
|
149
|
-
return uris;
|
|
150
|
-
}
|
|
151
|
-
|
|
152
|
-
/** Every text edit's `newText` in a {@link WorkspaceEdit}, concatenated in edit order. */
|
|
153
|
-
function insertedText(edit: WorkspaceEdit): string {
|
|
154
|
-
const parts: string[] = [];
|
|
155
|
-
for (const change of edit.documentChanges ?? []) {
|
|
156
|
-
if ('edits' in change) {
|
|
157
|
-
for (const textEdit of change.edits) {
|
|
158
|
-
if ('newText' in textEdit) {
|
|
159
|
-
parts.push(textEdit.newText);
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
for (const textEdits of Object.values(edit.changes ?? {})) {
|
|
165
|
-
for (const textEdit of textEdits) {
|
|
166
|
-
parts.push(textEdit.newText);
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
return parts.join('');
|
|
170
|
-
}
|
|
171
|
-
|
|
172
135
|
/**
|
|
173
136
|
* The in-process LSP transport seam — a real `Connection` over a crossed pair
|
|
174
137
|
* of in-memory streams, plus the client side, the diagnostics capture,
|
|
@@ -426,32 +389,7 @@ export function makeLspServerConnection(): LspServerConnection {
|
|
|
426
389
|
}
|
|
427
390
|
});
|
|
428
391
|
|
|
429
|
-
const
|
|
430
|
-
/**
|
|
431
|
-
* Waiters armed by {@link LspServerConnection.nextAppliedEdit}, fanned out to
|
|
432
|
-
* from the ONE handler below — `vscode-jsonrpc` keeps a single handler per
|
|
433
|
-
* REQUEST type just as it does per notification type, so a second
|
|
434
|
-
* `onRequest` would displace this one and leave the server's `applyEdit`
|
|
435
|
-
* unanswered.
|
|
436
|
-
*/
|
|
437
|
-
const appliedEditWaiters: Array<(edit: AppliedEdit) => boolean> = [];
|
|
438
|
-
let applyEditHandler: (params: ApplyWorkspaceEditParams) => ApplyWorkspaceEditResult = () => ({ applied: true });
|
|
439
|
-
const appliedEditSubscription = client.onRequest(ApplyWorkspaceEditRequest.type, (params: ApplyWorkspaceEditParams) => {
|
|
440
|
-
const edit: AppliedEdit = { params, uris: editedUris(params.edit), text: insertedText(params.edit) };
|
|
441
|
-
// Record BEFORE the handler runs, so a handler that rejects the edit or
|
|
442
|
-
// throws still leaves the push observable — the send is what the egress
|
|
443
|
-
// assertion is about.
|
|
444
|
-
appliedEdits.push(edit);
|
|
445
|
-
for (const waiter of [...appliedEditWaiters]) {
|
|
446
|
-
if (waiter(edit)) {
|
|
447
|
-
const at = appliedEditWaiters.indexOf(waiter);
|
|
448
|
-
if (at >= 0) {
|
|
449
|
-
appliedEditWaiters.splice(at, 1);
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
}
|
|
453
|
-
return applyEditHandler(params);
|
|
454
|
-
});
|
|
392
|
+
const appliedEditCapture = captureAppliedEdits(client, DEFAULT_DIAGNOSTICS_TIMEOUT_MS);
|
|
455
393
|
|
|
456
394
|
let disposed = false;
|
|
457
395
|
|
|
@@ -459,7 +397,7 @@ export function makeLspServerConnection(): LspServerConnection {
|
|
|
459
397
|
serverConnection,
|
|
460
398
|
client,
|
|
461
399
|
diagnostics,
|
|
462
|
-
appliedEdits,
|
|
400
|
+
appliedEdits: appliedEditCapture.appliedEdits,
|
|
463
401
|
wireBytes: () => ({
|
|
464
402
|
clientToServer: pair.clientToServer.bytes,
|
|
465
403
|
serverToClient: pair.serverToClient.bytes,
|
|
@@ -575,51 +513,16 @@ export function makeLspServerConnection(): LspServerConnection {
|
|
|
575
513
|
});
|
|
576
514
|
},
|
|
577
515
|
|
|
578
|
-
nextAppliedEdit
|
|
579
|
-
const options: NextAppliedEditOptions =
|
|
580
|
-
typeof timeoutMsOrOptions === 'number' ? { timeoutMs: timeoutMsOrOptions } : (timeoutMsOrOptions ?? {});
|
|
581
|
-
const timeoutMs = options.timeoutMs ?? DEFAULT_DIAGNOSTICS_TIMEOUT_MS;
|
|
582
|
-
const { fromIndex, match } = options;
|
|
583
|
-
const matches = (edit: AppliedEdit): boolean => edit.uris.includes(uri) && (match?.(edit) ?? true);
|
|
584
|
-
if (fromIndex !== undefined) {
|
|
585
|
-
// Replay from the capture first, for the same reason
|
|
586
|
-
// `nextDiagnostics` does: the push may already have arrived, and a
|
|
587
|
-
// waiter only ever sees later ones.
|
|
588
|
-
const captured = appliedEdits.slice(Math.max(0, fromIndex)).find(matches);
|
|
589
|
-
if (captured) {
|
|
590
|
-
return Promise.resolve(captured);
|
|
591
|
-
}
|
|
592
|
-
}
|
|
593
|
-
return new Promise<AppliedEdit>((resolve, reject) => {
|
|
594
|
-
const waiter = (edit: AppliedEdit): boolean => {
|
|
595
|
-
if (!matches(edit)) {
|
|
596
|
-
return false;
|
|
597
|
-
}
|
|
598
|
-
clearTimeout(timer);
|
|
599
|
-
resolve(edit);
|
|
600
|
-
return true;
|
|
601
|
-
};
|
|
602
|
-
const timer = setTimeout(() => {
|
|
603
|
-
const at = appliedEditWaiters.indexOf(waiter);
|
|
604
|
-
if (at >= 0) {
|
|
605
|
-
appliedEditWaiters.splice(at, 1);
|
|
606
|
-
}
|
|
607
|
-
reject(new Error(`Timed out waiting for a workspace/applyEdit addressing ${uri}`));
|
|
608
|
-
}, timeoutMs);
|
|
609
|
-
appliedEditWaiters.push(waiter);
|
|
610
|
-
});
|
|
611
|
-
},
|
|
516
|
+
nextAppliedEdit: appliedEditCapture.nextAppliedEdit,
|
|
612
517
|
|
|
613
|
-
setApplyEditHandler
|
|
614
|
-
applyEditHandler = handler;
|
|
615
|
-
},
|
|
518
|
+
setApplyEditHandler: appliedEditCapture.setApplyEditHandler,
|
|
616
519
|
|
|
617
520
|
dispose(): void {
|
|
618
521
|
if (disposed) {
|
|
619
522
|
return;
|
|
620
523
|
}
|
|
621
524
|
disposed = true;
|
|
622
|
-
|
|
525
|
+
appliedEditCapture.dispose();
|
|
623
526
|
diagnosticsSubscription.dispose();
|
|
624
527
|
client.dispose();
|
|
625
528
|
serverConnection.dispose();
|
|
@@ -61,7 +61,8 @@ import {
|
|
|
61
61
|
type ProtocolConnection,
|
|
62
62
|
type PublishDiagnosticsParams
|
|
63
63
|
} from 'vscode-languageserver-protocol/node';
|
|
64
|
-
import {
|
|
64
|
+
import { captureAppliedEdits } from './applied-edit-capture.js';
|
|
65
|
+
import { type AppliedEdit, type NextAppliedEditOptions, type NextDiagnosticsOptions } from './lsp-server-connection.js';
|
|
65
66
|
|
|
66
67
|
/**
|
|
67
68
|
* Default bound on the `initialize` handshake, and the reason there is one at
|
|
@@ -195,6 +196,21 @@ export interface SpawnedServer extends Harness {
|
|
|
195
196
|
* whenever one action fans out.
|
|
196
197
|
*/
|
|
197
198
|
nextDiagnostics(uri: string, timeoutMsOrOptions?: number | NextDiagnosticsOptions): Promise<Diagnostic[]>;
|
|
199
|
+
/**
|
|
200
|
+
* Every `workspace/applyEdit` the child sent, in arrival order; append-only,
|
|
201
|
+
* each answered `{ applied: true }` as a real language client does.
|
|
202
|
+
*
|
|
203
|
+
* A Hydranium server mirrors an edit only for a document this connection has
|
|
204
|
+
* opened, so a write to one never sent `didOpen` here produces no entry. A
|
|
205
|
+
* second `applyEdit` handler registered on {@link connection} displaces this
|
|
206
|
+
* capture.
|
|
207
|
+
*/
|
|
208
|
+
readonly appliedEdits: ReadonlyArray<AppliedEdit>;
|
|
209
|
+
/**
|
|
210
|
+
* Resolve with the next captured `workspace/applyEdit` addressing `uri`,
|
|
211
|
+
* with the same `fromIndex` and `match` contract as the in-process tier's.
|
|
212
|
+
*/
|
|
213
|
+
nextAppliedEdit(uri: string, timeoutMsOrOptions?: number | NextAppliedEditOptions): Promise<AppliedEdit>;
|
|
198
214
|
/**
|
|
199
215
|
* Graceful `shutdown` / `exit`, each bounded, then `SIGKILL`. Resolves with
|
|
200
216
|
* the child's exit code — `null` when a signal ended it, which is itself the
|
|
@@ -303,6 +319,7 @@ export async function startSpawnedServer(options: SpawnedServerOptions): Promise
|
|
|
303
319
|
connection.onNotification(LogMessageNotification.type, params => {
|
|
304
320
|
logMessages.push(params);
|
|
305
321
|
});
|
|
322
|
+
const appliedEditCapture = captureAppliedEdits(connection, RESPONSE_TIMEOUT_MS);
|
|
306
323
|
connection.listen();
|
|
307
324
|
|
|
308
325
|
const killTimeoutMs = options.killTimeoutMs ?? DEFAULT_KILL_TIMEOUT_MS;
|
|
@@ -470,7 +487,10 @@ export async function startSpawnedServer(options: SpawnedServerOptions): Promise
|
|
|
470
487
|
waiters.add(notify);
|
|
471
488
|
});
|
|
472
489
|
},
|
|
490
|
+
appliedEdits: appliedEditCapture.appliedEdits,
|
|
491
|
+
nextAppliedEdit: appliedEditCapture.nextAppliedEdit,
|
|
473
492
|
dispose(): Promise<number | null> {
|
|
493
|
+
appliedEditCapture.dispose();
|
|
474
494
|
liveServers.delete(server);
|
|
475
495
|
disposal ??= terminate();
|
|
476
496
|
return disposal;
|
|
@@ -68,3 +68,30 @@ export function isConnectionGoneError(err: unknown): boolean {
|
|
|
68
68
|
const normalized = message.toLowerCase();
|
|
69
69
|
return CONNECTION_GONE_PATTERNS.some(pattern => pattern.test(normalized));
|
|
70
70
|
}
|
|
71
|
+
|
|
72
|
+
const SOCKET_GONE_CODES = new Set(['EPIPE', 'ECONNRESET']);
|
|
73
|
+
|
|
74
|
+
/** `write EPIPE` / `read ECONNRESET`, as a request's `ResponseError` copies the message. */
|
|
75
|
+
const SOCKET_GONE_PATTERN = /\b(epipe|econnreset)\b/;
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Whether `err` means the client a connection serves has gone: what
|
|
79
|
+
* {@link isConnectionGoneError} recognises, or a write to a socket the client
|
|
80
|
+
* closed (`EPIPE`, `ECONNRESET`), matched by code or, for a request, by the
|
|
81
|
+
* code's name in the message.
|
|
82
|
+
*
|
|
83
|
+
* Only for a call whose errors can come from that connection alone. A socket
|
|
84
|
+
* error names no peer, so where a file system or another socket can fail the
|
|
85
|
+
* same call, it is a genuine failure and {@link isConnectionGoneError} is the
|
|
86
|
+
* one to ask.
|
|
87
|
+
*/
|
|
88
|
+
export function isClientGoneError(err: unknown): boolean {
|
|
89
|
+
if (isConnectionGoneError(err)) {
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
if (err instanceof Error && 'code' in err && typeof err.code === 'string' && SOCKET_GONE_CODES.has(err.code)) {
|
|
93
|
+
return true;
|
|
94
|
+
}
|
|
95
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
96
|
+
return SOCKET_GONE_PATTERN.test(message.toLowerCase());
|
|
97
|
+
}
|