@ian-pascoe/pi-lsp 0.1.0 → 0.2.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/README.md +14 -5
- package/package.json +10 -6
- package/skills/pi-lsp/SKILL.md +16 -0
- package/src/lsp-position-encoding.ts +2 -1
- package/src/lsp-post-edit-diagnostics-rendering.ts +2 -1
- package/src/lsp-post-edit-diagnostics.ts +7 -33
- package/src/lsp-server-client.ts +67 -85
- package/src/lsp-server-manager.ts +53 -19
- package/src/lsp-session-files.ts +1 -23
- package/src/lsp-tool-contract.ts +7 -11
- package/src/lsp-tool-output.ts +22 -10
- package/src/lsp-tool-rendering.ts +1 -4
- package/src/lsp-tool.ts +44 -66
- package/src/lsp-workspace-edit.ts +22 -79
- package/src/pi-lsp-extension.ts +28 -25
- package/src/pi-lsp-settings.ts +151 -71
package/README.md
CHANGED
|
@@ -45,6 +45,7 @@ Pi LSP reads only the `lsp` key from Pi's global `settings.json` and trusted pro
|
|
|
45
45
|
"languageId": "typescriptreact"
|
|
46
46
|
}
|
|
47
47
|
],
|
|
48
|
+
"requireRootMarker": true,
|
|
48
49
|
"rootMarkers": ["tsconfig.json", "package.json", ".git"],
|
|
49
50
|
"initializationOptions": {},
|
|
50
51
|
"settings": {},
|
|
@@ -58,14 +59,18 @@ Pi LSP reads only the `lsp` key from Pi's global `settings.json` and trusted pro
|
|
|
58
59
|
Every field is optional except an enabled server's non-empty `command` and `languages`. Each
|
|
59
60
|
language needs a non-empty `languageId` and at least one extension or exact filename. Extensions
|
|
60
61
|
include their leading period. `rootMarkers` are basename glob patterns; the nearest matching
|
|
61
|
-
ancestor becomes the server root and Pi's working directory is the fallback.
|
|
62
|
+
ancestor becomes the server root and Pi's working directory is the fallback. Set
|
|
63
|
+
`requireRootMarker` to `true` to exclude the server for files without any matching ancestor; it
|
|
64
|
+
defaults to `false`. A required empty `rootMarkers` list is invalid. Explicit requests naming an
|
|
65
|
+
otherwise compatible excluded server report that its required root marker was not found.
|
|
62
66
|
|
|
63
67
|
Global and project timeouts merge by field. A project server replaces the complete global server
|
|
64
68
|
with the same ID; set a project server to `null` to remove it. `initializationOptions` is sent only
|
|
65
69
|
during initialization. `settings` is used for `workspace/didChangeConfiguration` and
|
|
66
70
|
`workspace/configuration`. Environment strings override `process.env`; `null` removes a variable.
|
|
67
|
-
|
|
68
|
-
|
|
71
|
+
Invalid server definitions and timeout fields are quarantined individually and remain visible
|
|
72
|
+
through `status`; unrelated valid settings continue to work. An invalid project server replacement
|
|
73
|
+
still shadows the global definition. Untrusted project settings are ignored.
|
|
69
74
|
|
|
70
75
|
Pi's `/reload` reloads configuration. Servers start on first use, live for one Pi session, and stay
|
|
71
76
|
unavailable after a process or protocol failure until `restart` or `/reload`.
|
|
@@ -97,8 +102,9 @@ UTF-32 encoding. `selection_ranges` accepts a `positions` array. `find_reference
|
|
|
97
102
|
`workspace_symbols` requires `query` and a root-anchor `file_path`. `workspace_diagnostics`,
|
|
98
103
|
`capabilities`, and `restart` require `server_id` and a root-anchor `file_path`. Other reads query
|
|
99
104
|
every matching capable server unless narrowed by `server_id`; successful responses remain visible
|
|
100
|
-
when another server fails.
|
|
101
|
-
server
|
|
105
|
+
when another server fails. Automatic reads omit matching incapable servers and fail once if none
|
|
106
|
+
are capable; explicitly selecting an incapable server reports that the operation is unsupported. A
|
|
107
|
+
mutation may omit `server_id` only when exactly one matching capable server exists.
|
|
102
108
|
|
|
103
109
|
Formatting requires `tab_size` and `insert_spaces`. It optionally accepts
|
|
104
110
|
`trim_trailing_whitespace`, `insert_final_newline`, and `trim_final_newlines`. Range formatting also
|
|
@@ -164,6 +170,9 @@ Changed, created, and renamed destination files are diagnosed; deleted files are
|
|
|
164
170
|
recognized result gets an explicit outcome, including `no diagnostics`, `no configured server`,
|
|
165
171
|
timeout, unavailable server, or an `apply_patch` adapter-version warning. Diagnostics preserve
|
|
166
172
|
duplicates from independent servers and never change the original tool's success or error state.
|
|
173
|
+
Only servers that advertise document diagnostics participate; formatting-only servers remain
|
|
174
|
+
available for explicit LSP formatting operations without appearing in Post-edit Diagnostics.
|
|
175
|
+
Files excluded by every matching server's Activation Gate are skipped silently.
|
|
167
176
|
|
|
168
177
|
Findings, matched-server failures, timeouts, and adapter warnings also appear in one expandable
|
|
169
178
|
Post-edit Diagnostics Entry after the current tool batch. Clean results and files without a
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ian-pascoe/pi-lsp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Configured language-server tools and post-edit diagnostics for Pi",
|
|
6
6
|
"keywords": [
|
|
@@ -22,6 +22,7 @@
|
|
|
22
22
|
},
|
|
23
23
|
"files": [
|
|
24
24
|
"src",
|
|
25
|
+
"skills",
|
|
25
26
|
"README.md",
|
|
26
27
|
"LICENSE"
|
|
27
28
|
],
|
|
@@ -30,10 +31,6 @@
|
|
|
30
31
|
"access": "public",
|
|
31
32
|
"provenance": true
|
|
32
33
|
},
|
|
33
|
-
"scripts": {
|
|
34
|
-
"test": "vitest run --config ../../vitest.config.ts --root .",
|
|
35
|
-
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
36
|
-
},
|
|
37
34
|
"dependencies": {
|
|
38
35
|
"cross-spawn": "^7.0.6",
|
|
39
36
|
"vscode-languageserver-protocol": "^3.17.5"
|
|
@@ -52,6 +49,13 @@
|
|
|
52
49
|
"pi": {
|
|
53
50
|
"extensions": [
|
|
54
51
|
"./src/index.ts"
|
|
52
|
+
],
|
|
53
|
+
"skills": [
|
|
54
|
+
"./skills"
|
|
55
55
|
]
|
|
56
|
+
},
|
|
57
|
+
"scripts": {
|
|
58
|
+
"test": "vitest run --config ../../vitest.config.ts --root .",
|
|
59
|
+
"typecheck": "tsc --noEmit -p tsconfig.json"
|
|
56
60
|
}
|
|
57
|
-
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: pi-lsp
|
|
3
|
+
description: Configure or diagnose Pi LSP when a Server Definition fails, a file does not route, diagnostics are missing, or lsp settings need changing.
|
|
4
|
+
license: MIT
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Pi LSP
|
|
8
|
+
|
|
9
|
+
1. Read [`../../README.md`](../../README.md)'s Settings and `lsp` tool sections, then identify the effective settings scope.
|
|
10
|
+
2. Call `lsp` with `{"operation":"status"}`.
|
|
11
|
+
3. Test a representative file with `capabilities`, then `diagnostics`, supplying `server_id` when needed.
|
|
12
|
+
4. Classify the result as settings, routing, process, capability, or Post-edit Diagnostics behavior.
|
|
13
|
+
5. If settings changed, reload Pi; if settings are unchanged and the Server Instance is unavailable, use `restart`.
|
|
14
|
+
6. Repeat status, capabilities, and diagnostics. Finish when the representative file reaches the intended Server Instance and operation, or an exact unsupported capability is evidenced.
|
|
15
|
+
|
|
16
|
+
`configured` has not routed a file yet. `unavailable` is sticky and retains stderr. Keep diagnosis read-only; stop before applying a Workspace Edit Preview.
|
|
@@ -24,7 +24,8 @@ export function normalizeLspPositionEncoding(encoding: string | undefined): LspP
|
|
|
24
24
|
return "utf-16";
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
/** Split document text into lines on CRLF, LF, or CR separators. */
|
|
28
|
+
export function documentLines(documentText: string): readonly string[] {
|
|
28
29
|
return documentText.split(/\r\n|[\n\r]/u);
|
|
29
30
|
}
|
|
30
31
|
|
|
@@ -68,7 +68,8 @@ function severityColor(severity: DiagnosticSeverity): ThemeColor {
|
|
|
68
68
|
}
|
|
69
69
|
}
|
|
70
70
|
|
|
71
|
-
|
|
71
|
+
/** Render one count with a pluralized noun, e.g. `3 files` or `1 file`. */
|
|
72
|
+
export function pluralizedCount(count: number, singular: string, plural = `${singular}s`): string {
|
|
72
73
|
return `${count} ${count === 1 ? singular : plural}`;
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -98,28 +98,9 @@ export const PostEditDiagnosticOutcomeSchema = Type.Union([
|
|
|
98
98
|
export type PostEditDiagnosticOutcome = Static<typeof PostEditDiagnosticOutcomeSchema>;
|
|
99
99
|
|
|
100
100
|
/** Runs fresh Post-edit Diagnostics for changed paths after a Supported Mutation Tool result. */
|
|
101
|
-
export
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
paths: readonly PostEditDiagnosticPath[],
|
|
105
|
-
): Promise<readonly PostEditDiagnosticOutcome[]>;
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/** Minimal Tool Result shape accepted by the structural post-edit adapters. */
|
|
109
|
-
export interface PostEditToolResult {
|
|
110
|
-
/** Tool name supplied by Pi's central tool-result event. */
|
|
111
|
-
readonly toolName: string;
|
|
112
|
-
/** Original tool arguments supplied to the central event. */
|
|
113
|
-
readonly input: ToolResultEvent["input"];
|
|
114
|
-
/** Tool-result details owned by the mutation implementation. */
|
|
115
|
-
readonly details: ToolResultEvent["details"];
|
|
116
|
-
/** Existing Pi content, retained verbatim before the appended LSP section. */
|
|
117
|
-
readonly content: ToolResultEvent["content"];
|
|
118
|
-
/** Existing tool failure state, which diagnostics must not change. */
|
|
119
|
-
readonly isError: boolean;
|
|
120
|
-
/** Existing usage accounting, which diagnostics must not change. */
|
|
121
|
-
readonly usage?: ToolResultEvent["usage"];
|
|
122
|
-
}
|
|
101
|
+
export type PostEditDiagnosticsRunner = (
|
|
102
|
+
paths: readonly PostEditDiagnosticPath[],
|
|
103
|
+
) => Promise<readonly PostEditDiagnosticOutcome[]>;
|
|
123
104
|
|
|
124
105
|
/** Tool-result fields returned by Post-edit Diagnostics middleware without changing mutation state. */
|
|
125
106
|
export interface PostEditDiagnosticsResultPatch {
|
|
@@ -174,7 +155,7 @@ function pathsAfterMutation(result: ReturnType<typeof mutationResult>): PostEdit
|
|
|
174
155
|
|
|
175
156
|
/** Extract exact changed destination paths from one Supported Mutation Tool result. */
|
|
176
157
|
export function extractPostEditDiagnosticPaths(
|
|
177
|
-
event: Pick<
|
|
158
|
+
event: Pick<ToolResultEvent, "toolName" | "input" | "details" | "isError">,
|
|
178
159
|
): ExtractedMutation | undefined {
|
|
179
160
|
if (event.toolName === "edit" || event.toolName === "write") {
|
|
180
161
|
if (event.isError || !Value.Check(NativeMutationInputSchema, event.input)) return undefined;
|
|
@@ -261,7 +242,7 @@ export function formatPostEditDiagnostics(outcomes: readonly PostEditDiagnosticO
|
|
|
261
242
|
|
|
262
243
|
/** Append fresh Post-edit Diagnostics while preserving every mutation-result field Pi already owns. */
|
|
263
244
|
export async function appendPostEditDiagnostics(
|
|
264
|
-
event:
|
|
245
|
+
event: ToolResultEvent,
|
|
265
246
|
diagnostics: PostEditDiagnosticsRunner,
|
|
266
247
|
): Promise<PostEditDiagnosticsResultPatch | undefined> {
|
|
267
248
|
const extracted = extractPostEditDiagnosticPaths(event);
|
|
@@ -271,8 +252,9 @@ export async function appendPostEditDiagnostics(
|
|
|
271
252
|
kind: "warning",
|
|
272
253
|
message,
|
|
273
254
|
})),
|
|
274
|
-
...(await diagnostics
|
|
255
|
+
...(await diagnostics(extracted.paths)),
|
|
275
256
|
];
|
|
257
|
+
if (outcomes.length === 0) return undefined;
|
|
276
258
|
const patch: PostEditDiagnosticsResultPatch = {
|
|
277
259
|
content: [...event.content, { type: "text", text: formatPostEditDiagnostics(outcomes) }],
|
|
278
260
|
details: event.details,
|
|
@@ -281,11 +263,3 @@ export async function appendPostEditDiagnostics(
|
|
|
281
263
|
};
|
|
282
264
|
return event.usage === undefined ? patch : { ...patch, usage: event.usage };
|
|
283
265
|
}
|
|
284
|
-
|
|
285
|
-
/** Adapt Pi's central ToolResultEvent shape to Post-edit Diagnostics middleware. */
|
|
286
|
-
export async function appendPiPostEditDiagnostics(
|
|
287
|
-
event: ToolResultEvent,
|
|
288
|
-
diagnostics: PostEditDiagnosticsRunner,
|
|
289
|
-
): Promise<PostEditDiagnosticsResultPatch | undefined> {
|
|
290
|
-
return appendPostEditDiagnostics(event, diagnostics);
|
|
291
|
-
}
|
package/src/lsp-server-client.ts
CHANGED
|
@@ -48,10 +48,12 @@ import {
|
|
|
48
48
|
type WorkspaceEdit,
|
|
49
49
|
} from "vscode-languageserver-protocol/node";
|
|
50
50
|
import {
|
|
51
|
+
documentLines,
|
|
51
52
|
measureLspPositionCharacters,
|
|
52
53
|
normalizeLspPositionEncoding,
|
|
53
54
|
type LspPositionEncoding,
|
|
54
55
|
} from "./lsp-position-encoding.js";
|
|
56
|
+
import type { LspTimeouts } from "./pi-lsp-settings.js";
|
|
55
57
|
|
|
56
58
|
const MAX_OPEN_DOCUMENTS = 100;
|
|
57
59
|
const MAX_STDERR_BYTES = 1024 * 1024;
|
|
@@ -79,16 +81,7 @@ const PrepareRenameProviderSchema = Type.Object(
|
|
|
79
81
|
);
|
|
80
82
|
|
|
81
83
|
/** Time budgets, in milliseconds, for one language-server process. */
|
|
82
|
-
export
|
|
83
|
-
/** Initialize request budget. */
|
|
84
|
-
readonly initializeMs: number;
|
|
85
|
-
/** Ordinary request budget. */
|
|
86
|
-
readonly requestMs: number;
|
|
87
|
-
/** Fresh diagnostics budget. */
|
|
88
|
-
readonly diagnosticsMs: number;
|
|
89
|
-
/** Graceful shutdown budget before process termination. */
|
|
90
|
-
readonly shutdownMs: number;
|
|
91
|
-
}
|
|
84
|
+
export type LspServerClientTimeouts = LspTimeouts;
|
|
92
85
|
|
|
93
86
|
/** Launch and protocol configuration for one language-server process. */
|
|
94
87
|
export interface LspServerClientOptions {
|
|
@@ -118,8 +111,6 @@ export interface LspServerClientOptions {
|
|
|
118
111
|
|
|
119
112
|
/** A valid UTF-8 document synchronized with one server instance. */
|
|
120
113
|
export interface LspSynchronizedDocument {
|
|
121
|
-
/** Absolute file path. */
|
|
122
|
-
readonly filePath: string;
|
|
123
114
|
/** File URI sent to the server. */
|
|
124
115
|
readonly uri: string;
|
|
125
116
|
/** Monotonic document version local to this server instance. */
|
|
@@ -148,8 +139,6 @@ export type LspWorkspaceDiagnosticResult =
|
|
|
148
139
|
|
|
149
140
|
/** Classified process, protocol, timeout, cancellation, and UTF-8 client failure. */
|
|
150
141
|
export class LspServerClientError extends Error {
|
|
151
|
-
readonly _tag = "LspServerClientError" as const;
|
|
152
|
-
|
|
153
142
|
/** Construct a stable Pi LSP client failure that always names the stderr capture. */
|
|
154
143
|
constructor(
|
|
155
144
|
readonly kind:
|
|
@@ -218,7 +207,7 @@ function serverWantsSave(capabilities: ServerCapabilities): boolean {
|
|
|
218
207
|
}
|
|
219
208
|
|
|
220
209
|
function protocolLineEndPosition(text: string, encoding: LspPositionEncoding): Position {
|
|
221
|
-
const lines = text
|
|
210
|
+
const lines = documentLines(text);
|
|
222
211
|
const lineText = lines.at(-1) ?? "";
|
|
223
212
|
return {
|
|
224
213
|
line: lines.length - 1,
|
|
@@ -268,13 +257,12 @@ export class LspServerClient {
|
|
|
268
257
|
private positionEncodingValue: LspPositionEncoding = "utf-16";
|
|
269
258
|
private textDocumentSyncKind: TextDocumentSyncKind = TextDocumentSyncKind.None;
|
|
270
259
|
private readonly openDocuments = new Map<string, OpenDocumentState>();
|
|
260
|
+
private readonly documentSynchronizations = new Map<string, Promise<LspSynchronizedDocument>>();
|
|
271
261
|
private readonly pushDiagnostics = new Map<string, PushDiagnosticsState>();
|
|
272
262
|
private readonly pullDiagnostics = new Map<string, PullDiagnosticsState>();
|
|
273
263
|
private readonly dynamicRegistrations = new Map<string, Registration>();
|
|
274
264
|
private readonly diagnosticWaiters = new Map<string, Set<() => void>>();
|
|
275
|
-
private readonly protocolMessages: string[] = [];
|
|
276
265
|
private diagnosticsRevision = 0;
|
|
277
|
-
private diagnosticsRefreshRevision = 0;
|
|
278
266
|
private stderrTail: Buffer<ArrayBufferLike> = Buffer.alloc(0);
|
|
279
267
|
private stderrWrite = Promise.resolve();
|
|
280
268
|
private closing = false;
|
|
@@ -381,11 +369,6 @@ export class LspServerClient {
|
|
|
381
369
|
return this.options.stderrPath;
|
|
382
370
|
}
|
|
383
371
|
|
|
384
|
-
/** Most recent bounded protocol log/show messages, oldest first. */
|
|
385
|
-
get recentProtocolMessages(): readonly string[] {
|
|
386
|
-
return this.protocolMessages;
|
|
387
|
-
}
|
|
388
|
-
|
|
389
372
|
/** Whether a static or dynamically registered LSP method is available. */
|
|
390
373
|
hasCapability(method: string): boolean {
|
|
391
374
|
if (
|
|
@@ -525,8 +508,33 @@ export class LspServerClient {
|
|
|
525
508
|
filePath: string,
|
|
526
509
|
languageId: string,
|
|
527
510
|
): Promise<LspSynchronizedDocument> {
|
|
528
|
-
this.throwIfUnavailable();
|
|
529
511
|
const absolutePath = resolve(filePath);
|
|
512
|
+
const uri = pathToFileURL(absolutePath).href;
|
|
513
|
+
const activeSynchronization = this.documentSynchronizations.get(uri);
|
|
514
|
+
const waitForActiveSynchronization =
|
|
515
|
+
activeSynchronization?.then(
|
|
516
|
+
() => undefined,
|
|
517
|
+
() => undefined,
|
|
518
|
+
) ?? Promise.resolve();
|
|
519
|
+
const synchronization = waitForActiveSynchronization.then(() =>
|
|
520
|
+
this.synchronizeDocumentOnce(absolutePath, uri, languageId),
|
|
521
|
+
);
|
|
522
|
+
this.documentSynchronizations.set(uri, synchronization);
|
|
523
|
+
try {
|
|
524
|
+
return await synchronization;
|
|
525
|
+
} finally {
|
|
526
|
+
if (this.documentSynchronizations.get(uri) === synchronization) {
|
|
527
|
+
this.documentSynchronizations.delete(uri);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
private async synchronizeDocumentOnce(
|
|
533
|
+
absolutePath: string,
|
|
534
|
+
uri: string,
|
|
535
|
+
languageId: string,
|
|
536
|
+
): Promise<LspSynchronizedDocument> {
|
|
537
|
+
this.throwIfUnavailable();
|
|
530
538
|
let text: string;
|
|
531
539
|
try {
|
|
532
540
|
const bytes = await readFile(absolutePath);
|
|
@@ -544,10 +552,13 @@ export class LspServerClient {
|
|
|
544
552
|
throw cause;
|
|
545
553
|
}
|
|
546
554
|
|
|
547
|
-
const uri = pathToFileURL(absolutePath).href;
|
|
548
555
|
const existing = this.openDocuments.get(uri);
|
|
556
|
+
if (existing?.text === text && existing.languageId === languageId) {
|
|
557
|
+
this.openDocuments.delete(uri);
|
|
558
|
+
this.openDocuments.set(uri, existing);
|
|
559
|
+
return existing;
|
|
560
|
+
}
|
|
549
561
|
const next: OpenDocumentState = {
|
|
550
|
-
filePath: absolutePath,
|
|
551
562
|
uri,
|
|
552
563
|
version: (existing?.version ?? 0) + 1,
|
|
553
564
|
text,
|
|
@@ -622,7 +633,12 @@ export class LspServerClient {
|
|
|
622
633
|
}
|
|
623
634
|
|
|
624
635
|
try {
|
|
625
|
-
return await this.
|
|
636
|
+
return await this.raceBudget(
|
|
637
|
+
Promise.any(candidates),
|
|
638
|
+
this.options.timeouts.diagnosticsMs,
|
|
639
|
+
"diagnostics",
|
|
640
|
+
signal,
|
|
641
|
+
);
|
|
626
642
|
} catch (cause) {
|
|
627
643
|
if (cause instanceof LspServerClientError) {
|
|
628
644
|
if (cause.kind === "cancelled") throw cause;
|
|
@@ -805,22 +821,13 @@ export class LspServerClient {
|
|
|
805
821
|
}
|
|
806
822
|
});
|
|
807
823
|
this.connection.onRequest(WorkDoneProgressCreateRequest.type, () => undefined);
|
|
808
|
-
this.connection.onRequest(DiagnosticRefreshRequest.type, () =>
|
|
809
|
-
this.diagnosticsRefreshRevision++;
|
|
810
|
-
});
|
|
824
|
+
this.connection.onRequest(DiagnosticRefreshRequest.type, () => undefined);
|
|
811
825
|
this.connection.onRequest(ApplyWorkspaceEditRequest.type, async (parameters) =>
|
|
812
826
|
this.rejectServerWorkspaceEdit(parameters),
|
|
813
827
|
);
|
|
814
|
-
this.connection.onRequest(ShowMessageRequest.type, (
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
});
|
|
818
|
-
this.connection.onNotification(LogMessageNotification.type, (parameters) => {
|
|
819
|
-
this.rememberProtocolMessage(parameters.message);
|
|
820
|
-
});
|
|
821
|
-
this.connection.onNotification(ShowMessageNotification.type, (parameters) => {
|
|
822
|
-
this.rememberProtocolMessage(parameters.message);
|
|
823
|
-
});
|
|
828
|
+
this.connection.onRequest(ShowMessageRequest.type, () => null);
|
|
829
|
+
this.connection.onNotification(LogMessageNotification.type, () => undefined);
|
|
830
|
+
this.connection.onNotification(ShowMessageNotification.type, () => undefined);
|
|
824
831
|
}
|
|
825
832
|
|
|
826
833
|
private async initialize(): Promise<void> {
|
|
@@ -923,30 +930,19 @@ export class LspServerClient {
|
|
|
923
930
|
if (signal?.aborted === true) throw abortError(this.options);
|
|
924
931
|
|
|
925
932
|
const cancellation = new CancellationTokenSource();
|
|
926
|
-
let timeout: NodeJS.Timeout | undefined;
|
|
927
|
-
let abortListener: (() => void) | undefined;
|
|
928
|
-
const request = this.connection.sendRequest<TResult>(method, parameters, cancellation.token);
|
|
929
|
-
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
|
930
|
-
timeout = setTimeout(() => {
|
|
931
|
-
cancellation.cancel();
|
|
932
|
-
reject(timeoutError(this.options, operation));
|
|
933
|
-
}, budgetMs);
|
|
934
|
-
timeout.unref();
|
|
935
|
-
});
|
|
936
|
-
const abortPromise = new Promise<never>((_resolve, reject) => {
|
|
937
|
-
if (signal === undefined) return;
|
|
938
|
-
abortListener = () => {
|
|
939
|
-
cancellation.cancel();
|
|
940
|
-
reject(abortError(this.options));
|
|
941
|
-
};
|
|
942
|
-
signal.addEventListener("abort", abortListener, { once: true });
|
|
943
|
-
});
|
|
944
|
-
const terminalPromise = this.terminalErrorPromise.then((error) => {
|
|
945
|
-
throw error;
|
|
946
|
-
});
|
|
947
|
-
|
|
948
933
|
try {
|
|
949
|
-
return await Promise.race([
|
|
934
|
+
return await Promise.race([
|
|
935
|
+
this.raceBudget(
|
|
936
|
+
this.connection.sendRequest<TResult>(method, parameters, cancellation.token),
|
|
937
|
+
budgetMs,
|
|
938
|
+
operation,
|
|
939
|
+
signal,
|
|
940
|
+
() => cancellation.cancel(),
|
|
941
|
+
),
|
|
942
|
+
this.terminalErrorPromise.then((error) => {
|
|
943
|
+
throw error;
|
|
944
|
+
}),
|
|
945
|
+
]);
|
|
950
946
|
} catch (cause) {
|
|
951
947
|
if (cause instanceof LspServerClientError) throw cause;
|
|
952
948
|
throw new LspServerClientError(
|
|
@@ -957,10 +953,6 @@ export class LspServerClient {
|
|
|
957
953
|
{ cause },
|
|
958
954
|
);
|
|
959
955
|
} finally {
|
|
960
|
-
if (timeout !== undefined) clearTimeout(timeout);
|
|
961
|
-
if (signal !== undefined && abortListener !== undefined) {
|
|
962
|
-
signal.removeEventListener("abort", abortListener);
|
|
963
|
-
}
|
|
964
956
|
cancellation.dispose();
|
|
965
957
|
}
|
|
966
958
|
}
|
|
@@ -1047,34 +1039,29 @@ export class LspServerClient {
|
|
|
1047
1039
|
return { status: "fresh", source: "document_pull", diagnostics: report.items };
|
|
1048
1040
|
}
|
|
1049
1041
|
|
|
1050
|
-
private async firstDiagnosticResult(
|
|
1051
|
-
candidates: readonly Promise<LspDocumentDiagnosticResult>[],
|
|
1052
|
-
signal?: AbortSignal,
|
|
1053
|
-
): Promise<LspDocumentDiagnosticResult> {
|
|
1054
|
-
return this.raceBudget(
|
|
1055
|
-
Promise.any(candidates),
|
|
1056
|
-
this.options.timeouts.diagnosticsMs,
|
|
1057
|
-
"diagnostics",
|
|
1058
|
-
signal,
|
|
1059
|
-
);
|
|
1060
|
-
}
|
|
1061
|
-
|
|
1062
1042
|
private async raceBudget<TResult>(
|
|
1063
1043
|
value: Promise<TResult>,
|
|
1064
1044
|
budgetMs: number,
|
|
1065
1045
|
operation: string,
|
|
1066
1046
|
signal?: AbortSignal,
|
|
1047
|
+
onCancel?: () => void,
|
|
1067
1048
|
): Promise<TResult> {
|
|
1068
1049
|
if (signal?.aborted === true) throw abortError(this.options);
|
|
1069
1050
|
let timeout: NodeJS.Timeout | undefined;
|
|
1070
1051
|
let abortListener: (() => void) | undefined;
|
|
1071
1052
|
const timeoutPromise = new Promise<never>((_resolve, reject) => {
|
|
1072
|
-
timeout = setTimeout(() =>
|
|
1053
|
+
timeout = setTimeout(() => {
|
|
1054
|
+
onCancel?.();
|
|
1055
|
+
reject(timeoutError(this.options, operation));
|
|
1056
|
+
}, budgetMs);
|
|
1073
1057
|
timeout.unref();
|
|
1074
1058
|
});
|
|
1075
1059
|
const abortPromise = new Promise<never>((_resolve, reject) => {
|
|
1076
1060
|
if (signal === undefined) return;
|
|
1077
|
-
abortListener = () =>
|
|
1061
|
+
abortListener = () => {
|
|
1062
|
+
onCancel?.();
|
|
1063
|
+
reject(abortError(this.options));
|
|
1064
|
+
};
|
|
1078
1065
|
signal.addEventListener("abort", abortListener, { once: true });
|
|
1079
1066
|
});
|
|
1080
1067
|
try {
|
|
@@ -1167,11 +1154,6 @@ export class LspServerClient {
|
|
|
1167
1154
|
}
|
|
1168
1155
|
}
|
|
1169
1156
|
|
|
1170
|
-
private rememberProtocolMessage(message: string): void {
|
|
1171
|
-
this.protocolMessages.push(message);
|
|
1172
|
-
if (this.protocolMessages.length > 100) this.protocolMessages.shift();
|
|
1173
|
-
}
|
|
1174
|
-
|
|
1175
1157
|
private async evictOldDocuments(): Promise<void> {
|
|
1176
1158
|
while (this.openDocuments.size > MAX_OPEN_DOCUMENTS) {
|
|
1177
1159
|
const oldestUri = this.openDocuments.keys().next().value;
|