@adecore/lsp 0.0.1 → 0.17.0-beta.1
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 +110 -0
- package/README.md +33 -0
- package/dist/connection.d.ts +50 -0
- package/dist/connection.js +290 -0
- package/dist/document.d.ts +60 -0
- package/dist/document.js +282 -0
- package/dist/edits.d.ts +18 -0
- package/dist/edits.js +161 -0
- package/dist/file-operations.d.ts +5 -0
- package/dist/file-operations.js +23 -0
- package/dist/glob.d.ts +1 -0
- package/dist/glob.js +67 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.js +10 -0
- package/dist/protocol.d.ts +470 -0
- package/dist/protocol.js +1 -0
- package/dist/service.d.ts +53 -0
- package/dist/service.js +1 -0
- package/dist/session.d.ts +78 -0
- package/dist/session.js +528 -0
- package/dist/test-transport.d.ts +33 -0
- package/dist/test-transport.js +72 -0
- package/dist/testing.d.ts +33 -0
- package/dist/testing.js +72 -0
- package/dist/transport.d.ts +27 -0
- package/dist/transport.js +258 -0
- package/dist/uris.d.ts +2 -0
- package/dist/uris.js +28 -0
- package/dist/vue.d.ts +6 -0
- package/dist/vue.js +86 -0
- package/dist/watched-files.d.ts +3 -0
- package/dist/watched-files.js +41 -0
- package/package.json +36 -3
- package/src/connection.ts +310 -0
- package/src/document.ts +357 -0
- package/src/edits.ts +182 -0
- package/src/file-operations.ts +30 -0
- package/src/glob.ts +60 -0
- package/src/index.ts +30 -0
- package/src/protocol.ts +479 -0
- package/src/service.ts +109 -0
- package/src/session.ts +613 -0
- package/src/test-transport.ts +92 -0
- package/src/testing.ts +105 -0
- package/src/transport.ts +290 -0
- package/src/uris.ts +29 -0
- package/src/vue.ts +93 -0
- package/src/watched-files.ts +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, MIT Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-MIT
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 Bas Milius
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under
|
|
20
|
+
these Terms and Conditions, as indicated by our inclusion of these Terms and
|
|
21
|
+
Conditions with the Software.
|
|
22
|
+
|
|
23
|
+
### License Grant
|
|
24
|
+
|
|
25
|
+
Subject to your compliance with this License Grant and the Patents,
|
|
26
|
+
Redistribution and Trademark clauses below, we hereby grant you the right to
|
|
27
|
+
use, copy, modify, create derivative works, publicly perform, publicly display
|
|
28
|
+
and redistribute the Software for any Permitted Purpose identified below.
|
|
29
|
+
|
|
30
|
+
### Permitted Purpose
|
|
31
|
+
|
|
32
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use
|
|
33
|
+
means making the Software available to others in a commercial product or
|
|
34
|
+
service that:
|
|
35
|
+
|
|
36
|
+
1. substitutes for the Software;
|
|
37
|
+
|
|
38
|
+
2. substitutes for any other product or service we offer using the Software
|
|
39
|
+
that exists as of the date we make the Software available; or
|
|
40
|
+
|
|
41
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
42
|
+
|
|
43
|
+
Permitted Purposes specifically include using the Software:
|
|
44
|
+
|
|
45
|
+
1. for your internal use and access;
|
|
46
|
+
|
|
47
|
+
2. for non-commercial education;
|
|
48
|
+
|
|
49
|
+
3. for non-commercial research; and
|
|
50
|
+
|
|
51
|
+
4. in connection with professional services that you provide to a licensee
|
|
52
|
+
using the Software in accordance with these Terms and Conditions.
|
|
53
|
+
|
|
54
|
+
### Patents
|
|
55
|
+
|
|
56
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our
|
|
57
|
+
patents, the license grant above includes a license under our patents. If you
|
|
58
|
+
make a claim against any party that the Software infringes or contributes to
|
|
59
|
+
the infringement of any patent, then your patent license to the Software ends
|
|
60
|
+
immediately.
|
|
61
|
+
|
|
62
|
+
### Redistribution
|
|
63
|
+
|
|
64
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of
|
|
65
|
+
the Software.
|
|
66
|
+
|
|
67
|
+
If you redistribute any copies, modifications or derivatives of the Software,
|
|
68
|
+
you must include a copy of or a link to these Terms and Conditions and not
|
|
69
|
+
remove any copyright notices provided in or with the Software.
|
|
70
|
+
|
|
71
|
+
### Disclaimer
|
|
72
|
+
|
|
73
|
+
THE SOFTWARE IS PROVIDED "AS IS" AND WITHOUT WARRANTIES OF ANY KIND, EXPRESS OR
|
|
74
|
+
IMPLIED, INCLUDING WITHOUT LIMITATION WARRANTIES OF FITNESS FOR A PARTICULAR
|
|
75
|
+
PURPOSE, MERCHANTABILITY, TITLE OR NON-INFRINGEMENT.
|
|
76
|
+
|
|
77
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE
|
|
78
|
+
SOFTWARE, INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES,
|
|
79
|
+
EVEN IF WE HAVE BEEN INFORMED OF THEIR POSSIBILITY IN ADVANCE.
|
|
80
|
+
|
|
81
|
+
### Trademarks
|
|
82
|
+
|
|
83
|
+
Except for displaying the License Details and identifying us as the origin of
|
|
84
|
+
the Software, you have no right under these Terms and Conditions to use our
|
|
85
|
+
trademarks, trade names, service marks or product names.
|
|
86
|
+
|
|
87
|
+
## Grant of Future License
|
|
88
|
+
|
|
89
|
+
We hereby irrevocably grant you an additional license to use the Software under
|
|
90
|
+
the MIT license that is effective on the second anniversary of the date we make
|
|
91
|
+
the Software available. On or after that date, you may use the Software under
|
|
92
|
+
the MIT license, in which case the following will apply:
|
|
93
|
+
|
|
94
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
95
|
+
this software and associated documentation files (the "Software"), to deal in
|
|
96
|
+
the Software without restriction, including without limitation the rights to
|
|
97
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
|
98
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
|
99
|
+
so, subject to the following conditions:
|
|
100
|
+
|
|
101
|
+
The above copyright notice and this permission notice shall be included in all
|
|
102
|
+
copies or substantial portions of the Software.
|
|
103
|
+
|
|
104
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
105
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
106
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
107
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
108
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
109
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
110
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# @adecore/lsp
|
|
2
|
+
|
|
3
|
+
Read the [LSP handbook](https://adecore.dev/lsp/handbook/) for the complete setup, concepts, host integration and testing guides.
|
|
4
|
+
|
|
5
|
+
A DOM-free LSP 3.17 client, and the `LanguageService` interface the smart editor asks its language features through. It spawns nothing and imports no Node or Bun API outside its tests, so a host can use it in a server process or browser. The document model it feeds is `@adecore/editor-core`; the view is `@adecore/editor`.
|
|
6
|
+
|
|
7
|
+
Positions are LSP positions: zero-based lines and UTF-16 characters, with a line ending at `\n`, `\r\n` or a lone `\r`. Turning them into editor offsets is the view's job.
|
|
8
|
+
|
|
9
|
+
## API
|
|
10
|
+
|
|
11
|
+
Everything comes from the package root; `@adecore/lsp/testing` holds the test doubles.
|
|
12
|
+
|
|
13
|
+
- `LspSession`: one conversation with one server over an `LspTransport`. `initialize` negotiates capabilities (UTF-16 only, snippets off unless asked), `openDocument` returns an `LspDocument`, and `supports` / `providerOptions` answer from static capabilities and dynamic registrations alike. It answers the server's configuration, folder, registration, progress and message requests, and `onDiagnostics`, `onCapabilitiesChanged` (registrations and refresh requests), `onProgress`, `onNotification` and `onError` observe it. `shutdown` closes the documents, then sends `shutdown` and `exit`.
|
|
14
|
+
- `LspDocument`: one open file in one session, and the owner of its version. `applyChanges` takes `didChange` entries and sends them as they are to a server that negotiated incremental sync, or the whole text to one that negotiated full sync. `updateText` sends the one range that differs from the held text. Every call raises the version by one. A request is cancelled when the text changes or a newer one for the same feature is made (`cancelPrevious`), and an answer for an older version rejects with `StaleResultError`, so does resolving a completion item, code action, lens or hint of an older version. One method per feature: completion, hover, signature help, the four navigations, references, highlights, symbols, rename, code actions, code lenses, formatting, folding, semantic tokens (full, delta, range), inlay hints and pull diagnostics.
|
|
15
|
+
- `JsonRpcConnection`: correlation, `$/cancelRequest`, a request timeout (30 seconds, `0` turns it off) and the requests a server sends. `LspError` carries a JSON-RPC code; `ErrorCodes` names the ones used here.
|
|
16
|
+
- Transports: `createStreamTransport` over a `ByteStream` (a child's stdin and stdout, which the host adapts), `ContentLengthDecoder` and `encodeMessage` for the framing, `createMemoryTransportPair` and `connectWebSocket`.
|
|
17
|
+
- Edits: `applyTextEdits` and `planWorkspaceEdit` (simultaneous edits and a multi-file plan, which refuses file operations), `applyContentChanges` (sequential), `minimalChange`, `offsetAt`, `positionAt` and `endPosition`.
|
|
18
|
+
- File renames: `willRenameFiles` and `didRenameFiles` of `LspSession` take the files that move (`RenamedFile`, which says whether each is a folder) and pass on only those that the filters of the server take, from its capabilities and its registrations (`fileOperationFilters`, `renamesTaken`: scheme, glob on the path the file leaves, `matches`, `ignoreCase`). The session declares `resourceOperations: ['rename']`, `workspace.fileOperations` and the `refactor.move` kind in `initialize`.
|
|
19
|
+
- `bridgeVueTypeScript` relays Vue's `tsserver/request` to the TypeScript server, and `vueServerOrder` says which of the two servers of a `.vue` document to ask first.
|
|
20
|
+
- `pathToFileUri` and `fileUriToPath`.
|
|
21
|
+
- `LanguageService`: what the editor asks of the language side (open, change and close a document, every feature above, `executeCommand`, `supports`, `providerOptions`, `onProvidersChanged` and `onDiagnostics`). Results stay in LSP shapes. A request takes `signal` and `parallel`, which lets many requests of one feature run side by side instead of each taking over from the one before. A host adapts server sessions or its own transport to this interface. Server discovery, process startup, authorization and saving remain outside the library.
|
|
22
|
+
- `@adecore/lsp/testing`: `FakeLanguageServer`, a server on the far end of a transport that answers the handshake, mirrors document text and answers whatever a test registers, and `createMemoryTransportPair`.
|
|
23
|
+
|
|
24
|
+
## Known limits
|
|
25
|
+
|
|
26
|
+
- Unversioned diagnostics cannot be proven fresh and are accepted while their document is open.
|
|
27
|
+
- The package applies no workspace edit and touches no file. File creates, renames and deletes in a `WorkspaceEdit` are the host's, and the host only declares renames.
|
|
28
|
+
- Only UTF-16 position encoding is negotiated; a server that insists on another one fails to initialize.
|
|
29
|
+
- Servers that negotiated no document changes (`change: 0`) cannot follow an edit.
|
|
30
|
+
|
|
31
|
+
## Packaging
|
|
32
|
+
|
|
33
|
+
This unpublished package is private at version `0.0.0` and retains FSL-1.1-MIT. Source consumers enable `source` in the bundler and TypeScript `customConditions`. Default consumers use compiled JavaScript and declarations. Run `bun run build`, `bun run typecheck` and `bun run test` from the package after workspace installation. Tests use memory transports and fake servers; no installed language server is required.
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { Disposable, LspTransport, RequestOptions } from './protocol.ts';
|
|
2
|
+
export declare const ErrorCodes: {
|
|
3
|
+
readonly InvalidRequest: -32600;
|
|
4
|
+
readonly MethodNotFound: -32601;
|
|
5
|
+
readonly InternalError: -32603;
|
|
6
|
+
readonly ServerNotInitialized: -32002;
|
|
7
|
+
readonly RequestCancelled: -32800;
|
|
8
|
+
readonly ContentModified: -32801;
|
|
9
|
+
};
|
|
10
|
+
export declare class LspError extends Error {
|
|
11
|
+
readonly code: number;
|
|
12
|
+
readonly data?: unknown;
|
|
13
|
+
constructor(message: string, code?: number, data?: unknown);
|
|
14
|
+
}
|
|
15
|
+
export declare class StaleResultError extends LspError {
|
|
16
|
+
readonly uri: string;
|
|
17
|
+
constructor(uri: string);
|
|
18
|
+
}
|
|
19
|
+
export type RequestHandler = (params: unknown, signal: AbortSignal) => unknown | Promise<unknown>;
|
|
20
|
+
type NotificationHandler = (params: unknown) => void | Promise<void>;
|
|
21
|
+
export declare class JsonRpcConnection {
|
|
22
|
+
readonly transport: LspTransport;
|
|
23
|
+
readonly timeoutMs: number;
|
|
24
|
+
private nextId;
|
|
25
|
+
private closed;
|
|
26
|
+
private sendQueue;
|
|
27
|
+
private readonly pending;
|
|
28
|
+
private readonly incoming;
|
|
29
|
+
private readonly handlers;
|
|
30
|
+
private readonly notifications;
|
|
31
|
+
private readonly errors;
|
|
32
|
+
private readonly closes;
|
|
33
|
+
private readonly subscriptions;
|
|
34
|
+
constructor(transport: LspTransport, timeoutMs?: number);
|
|
35
|
+
get isClosed(): boolean;
|
|
36
|
+
request<T = unknown>(method: string, params?: unknown, options?: RequestOptions): Promise<T>;
|
|
37
|
+
notify(method: string, params?: unknown): Promise<void>;
|
|
38
|
+
onRequest(method: string, handler: RequestHandler): Disposable;
|
|
39
|
+
onNotification(method: string, listener: NotificationHandler): Disposable;
|
|
40
|
+
onError(listener: (error: Error) => void): Disposable;
|
|
41
|
+
onClose(listener: (error?: Error) => void): Disposable;
|
|
42
|
+
reportError(error: unknown): void;
|
|
43
|
+
close(): Promise<void>;
|
|
44
|
+
private send;
|
|
45
|
+
private settle;
|
|
46
|
+
private receive;
|
|
47
|
+
private handleRequest;
|
|
48
|
+
private finish;
|
|
49
|
+
}
|
|
50
|
+
export {};
|
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
/* The JSON-RPC and LSP codes this package answers with. */
|
|
2
|
+
export const ErrorCodes = {
|
|
3
|
+
InvalidRequest: -32600,
|
|
4
|
+
MethodNotFound: -32601,
|
|
5
|
+
InternalError: -32603,
|
|
6
|
+
ServerNotInitialized: -32002,
|
|
7
|
+
RequestCancelled: -32800,
|
|
8
|
+
ContentModified: -32801
|
|
9
|
+
};
|
|
10
|
+
export class LspError extends Error {
|
|
11
|
+
code;
|
|
12
|
+
data;
|
|
13
|
+
constructor(message, code = ErrorCodes.InternalError, data) {
|
|
14
|
+
super(message);
|
|
15
|
+
this.name = 'LspError';
|
|
16
|
+
this.code = code;
|
|
17
|
+
this.data = data;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
/* The document changed or closed before the answer arrived, so the answer describes a text that is gone. */
|
|
21
|
+
export class StaleResultError extends LspError {
|
|
22
|
+
uri;
|
|
23
|
+
constructor(uri) {
|
|
24
|
+
super(`The document changed before its language result arrived: ${uri}`, ErrorCodes.ContentModified);
|
|
25
|
+
this.name = 'StaleResultError';
|
|
26
|
+
this.uri = uri;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
function asError(error) {
|
|
30
|
+
return error instanceof Error ? error : new Error(String(error));
|
|
31
|
+
}
|
|
32
|
+
/* JSON-RPC 2.0 over an `LspTransport`: correlation, cancellation, and the requests a server sends back. */
|
|
33
|
+
export class JsonRpcConnection {
|
|
34
|
+
transport;
|
|
35
|
+
timeoutMs;
|
|
36
|
+
nextId = 0;
|
|
37
|
+
closed = false;
|
|
38
|
+
sendQueue = Promise.resolve();
|
|
39
|
+
pending = new Map();
|
|
40
|
+
incoming = new Map();
|
|
41
|
+
handlers = new Map();
|
|
42
|
+
notifications = new Map();
|
|
43
|
+
errors = new Set();
|
|
44
|
+
closes = new Set();
|
|
45
|
+
subscriptions;
|
|
46
|
+
/* A `timeoutMs` of 0 turns the deadline off; a request then ends by its reply or its signal. */
|
|
47
|
+
constructor(transport, timeoutMs = 30_000) {
|
|
48
|
+
this.transport = transport;
|
|
49
|
+
this.timeoutMs = timeoutMs;
|
|
50
|
+
this.subscriptions = [
|
|
51
|
+
transport.onMessage((message) => {
|
|
52
|
+
void this.receive(message).catch((error) => this.reportError(error));
|
|
53
|
+
}),
|
|
54
|
+
transport.onClose((error) => this.finish(error))
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
get isClosed() {
|
|
58
|
+
return this.closed;
|
|
59
|
+
}
|
|
60
|
+
request(method, params, options = {}) {
|
|
61
|
+
if (this.closed) {
|
|
62
|
+
return Promise.reject(new LspError('LSP connection is closed'));
|
|
63
|
+
}
|
|
64
|
+
if (options.signal?.aborted) {
|
|
65
|
+
return Promise.reject(new LspError('Request cancelled', ErrorCodes.RequestCancelled));
|
|
66
|
+
}
|
|
67
|
+
const id = ++this.nextId;
|
|
68
|
+
return new Promise((resolve, reject) => {
|
|
69
|
+
let timer;
|
|
70
|
+
const cancel = (error) => {
|
|
71
|
+
if (!this.pending.has(id)) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
this.settle(id, undefined, error);
|
|
75
|
+
void this.notify('$/cancelRequest', { id }).catch((failure) => this.reportError(failure));
|
|
76
|
+
};
|
|
77
|
+
const abort = () => cancel(new LspError('Request cancelled', ErrorCodes.RequestCancelled));
|
|
78
|
+
this.pending.set(id, {
|
|
79
|
+
resolve: (value) => resolve(value),
|
|
80
|
+
reject,
|
|
81
|
+
cleanup: () => {
|
|
82
|
+
clearTimeout(timer);
|
|
83
|
+
options.signal?.removeEventListener('abort', abort);
|
|
84
|
+
}
|
|
85
|
+
});
|
|
86
|
+
options.signal?.addEventListener('abort', abort, { once: true });
|
|
87
|
+
const timeout = options.timeoutMs ?? this.timeoutMs;
|
|
88
|
+
if (timeout > 0) {
|
|
89
|
+
timer = setTimeout(() => cancel(new LspError(`LSP request timed out: ${method}`, ErrorCodes.RequestCancelled)), timeout);
|
|
90
|
+
}
|
|
91
|
+
void this.send({ jsonrpc: '2.0', id, method, params }).catch((error) => this.settle(id, undefined, error));
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
notify(method, params) {
|
|
95
|
+
return this.send({ jsonrpc: '2.0', method, params });
|
|
96
|
+
}
|
|
97
|
+
onRequest(method, handler) {
|
|
98
|
+
if (this.handlers.has(method)) {
|
|
99
|
+
throw new Error(`LSP request handler already registered: ${method}`);
|
|
100
|
+
}
|
|
101
|
+
this.handlers.set(method, handler);
|
|
102
|
+
return {
|
|
103
|
+
dispose: () => {
|
|
104
|
+
if (this.handlers.get(method) === handler) {
|
|
105
|
+
this.handlers.delete(method);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
onNotification(method, listener) {
|
|
111
|
+
let listeners = this.notifications.get(method);
|
|
112
|
+
if (!listeners) {
|
|
113
|
+
listeners = new Set();
|
|
114
|
+
this.notifications.set(method, listeners);
|
|
115
|
+
}
|
|
116
|
+
const registered = listeners;
|
|
117
|
+
registered.add(listener);
|
|
118
|
+
return {
|
|
119
|
+
dispose: () => {
|
|
120
|
+
registered.delete(listener);
|
|
121
|
+
if (registered.size === 0) {
|
|
122
|
+
this.notifications.delete(method);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
onError(listener) {
|
|
128
|
+
this.errors.add(listener);
|
|
129
|
+
return {
|
|
130
|
+
dispose: () => {
|
|
131
|
+
this.errors.delete(listener);
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
onClose(listener) {
|
|
136
|
+
this.closes.add(listener);
|
|
137
|
+
if (this.closed) {
|
|
138
|
+
listener();
|
|
139
|
+
}
|
|
140
|
+
return {
|
|
141
|
+
dispose: () => {
|
|
142
|
+
this.closes.delete(listener);
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
reportError(error) {
|
|
147
|
+
const normalized = asError(error);
|
|
148
|
+
for (const listener of this.errors) {
|
|
149
|
+
try {
|
|
150
|
+
listener(normalized);
|
|
151
|
+
}
|
|
152
|
+
catch {
|
|
153
|
+
// An error observer must not break protocol dispatch.
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
async close() {
|
|
158
|
+
this.finish();
|
|
159
|
+
await this.transport.close();
|
|
160
|
+
}
|
|
161
|
+
send(message) {
|
|
162
|
+
const sent = this.sendQueue.then(async () => {
|
|
163
|
+
if (this.closed) {
|
|
164
|
+
throw new LspError('LSP connection is closed');
|
|
165
|
+
}
|
|
166
|
+
await this.transport.send(message);
|
|
167
|
+
});
|
|
168
|
+
this.sendQueue = sent.catch((error) => this.finish(asError(error)));
|
|
169
|
+
return sent;
|
|
170
|
+
}
|
|
171
|
+
settle(id, result, error) {
|
|
172
|
+
const request = this.pending.get(id);
|
|
173
|
+
if (!request) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
this.pending.delete(id);
|
|
177
|
+
request.cleanup();
|
|
178
|
+
if (error !== undefined) {
|
|
179
|
+
request.reject(error);
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
request.resolve(result);
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
async receive(value) {
|
|
186
|
+
if (this.closed) {
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
if (!value || typeof value !== 'object' || !('jsonrpc' in value) || value.jsonrpc !== '2.0') {
|
|
190
|
+
throw new LspError('Invalid JSON-RPC message', ErrorCodes.InvalidRequest);
|
|
191
|
+
}
|
|
192
|
+
const message = value;
|
|
193
|
+
if ('method' in message) {
|
|
194
|
+
if (typeof message.method !== 'string') {
|
|
195
|
+
throw new LspError('Invalid JSON-RPC method', ErrorCodes.InvalidRequest);
|
|
196
|
+
}
|
|
197
|
+
if ('id' in message) {
|
|
198
|
+
if (typeof message.id !== 'number' && typeof message.id !== 'string') {
|
|
199
|
+
throw new LspError('Invalid request id', ErrorCodes.InvalidRequest);
|
|
200
|
+
}
|
|
201
|
+
await this.handleRequest(message);
|
|
202
|
+
}
|
|
203
|
+
else if (message.method === '$/cancelRequest') {
|
|
204
|
+
const id = message.params?.id;
|
|
205
|
+
if (id !== undefined) {
|
|
206
|
+
this.incoming.get(id)?.abort();
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
else {
|
|
210
|
+
for (const listener of this.notifications.get(message.method) ?? []) {
|
|
211
|
+
try {
|
|
212
|
+
await listener(message.params);
|
|
213
|
+
}
|
|
214
|
+
catch (error) {
|
|
215
|
+
this.reportError(error);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
else if ('id' in message && message.id !== null) {
|
|
221
|
+
if (message.error) {
|
|
222
|
+
this.settle(message.id, undefined, new LspError(message.error.message, message.error.code, message.error.data));
|
|
223
|
+
}
|
|
224
|
+
else if ('result' in message) {
|
|
225
|
+
this.settle(message.id, message.result);
|
|
226
|
+
}
|
|
227
|
+
else {
|
|
228
|
+
this.settle(message.id, undefined, new LspError('Invalid JSON-RPC response', ErrorCodes.InvalidRequest));
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
else {
|
|
232
|
+
throw new LspError('Invalid JSON-RPC response', ErrorCodes.InvalidRequest);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
async handleRequest(message) {
|
|
236
|
+
const controller = new AbortController();
|
|
237
|
+
this.incoming.set(message.id, controller);
|
|
238
|
+
try {
|
|
239
|
+
const handler = this.handlers.get(message.method);
|
|
240
|
+
if (!handler) {
|
|
241
|
+
throw new LspError(`Unsupported server request: ${message.method}`, ErrorCodes.MethodNotFound);
|
|
242
|
+
}
|
|
243
|
+
const result = await handler(message.params, controller.signal);
|
|
244
|
+
if (controller.signal.aborted) {
|
|
245
|
+
throw new LspError('Request cancelled', ErrorCodes.RequestCancelled);
|
|
246
|
+
}
|
|
247
|
+
await this.send({ jsonrpc: '2.0', id: message.id, result: result ?? null });
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
const failure = error instanceof LspError ? error : new LspError(asError(error).message);
|
|
251
|
+
if (!this.closed) {
|
|
252
|
+
await this.send({ jsonrpc: '2.0', id: message.id, error: { code: failure.code, message: failure.message, data: failure.data } });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
finally {
|
|
256
|
+
this.incoming.delete(message.id);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
finish(error) {
|
|
260
|
+
if (this.closed) {
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
this.closed = true;
|
|
264
|
+
for (const id of [...this.pending.keys()]) {
|
|
265
|
+
this.settle(id, undefined, error ?? new LspError('LSP connection closed'));
|
|
266
|
+
}
|
|
267
|
+
for (const controller of this.incoming.values()) {
|
|
268
|
+
controller.abort();
|
|
269
|
+
}
|
|
270
|
+
this.incoming.clear();
|
|
271
|
+
// A transport that is closed already calls back from inside the constructor, before the subscriptions exist.
|
|
272
|
+
for (const subscription of this.subscriptions ?? []) {
|
|
273
|
+
subscription.dispose();
|
|
274
|
+
}
|
|
275
|
+
for (const listener of this.closes) {
|
|
276
|
+
try {
|
|
277
|
+
listener(error);
|
|
278
|
+
}
|
|
279
|
+
catch (failure) {
|
|
280
|
+
this.reportError(failure);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
this.handlers.clear();
|
|
284
|
+
this.notifications.clear();
|
|
285
|
+
this.closes.clear();
|
|
286
|
+
if (error) {
|
|
287
|
+
this.reportError(error);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { LspSession } from './session.ts';
|
|
2
|
+
import type { CodeAction, CodeActionContext, CodeLens, Command, CompletionContext, CompletionItem, CompletionResult, ContentChange, DocumentDiagnosticReport, DocumentHighlight, DocumentItem, DocumentRequestOptions, DocumentSymbolResult, FoldingRange, FormattingOptions, Hover, InlayHint, Location, NavigationResult, Position, PrepareRenameResult, Range, SelectionRange, SemanticTokens, SemanticTokensDelta, SignatureHelp, SignatureHelpContext, TextEdit, WorkspaceEdit } from './protocol.ts';
|
|
3
|
+
export declare class LspDocument {
|
|
4
|
+
readonly session: LspSession;
|
|
5
|
+
readonly uri: string;
|
|
6
|
+
readonly languageId: string;
|
|
7
|
+
readonly ready: Promise<void>;
|
|
8
|
+
private revision;
|
|
9
|
+
private content;
|
|
10
|
+
private closed;
|
|
11
|
+
private synced;
|
|
12
|
+
private closePromise?;
|
|
13
|
+
private readonly inFlight;
|
|
14
|
+
private readonly latest;
|
|
15
|
+
private readonly resultVersions;
|
|
16
|
+
constructor(session: LspSession, item: DocumentItem);
|
|
17
|
+
get version(): number;
|
|
18
|
+
get text(): string;
|
|
19
|
+
get isClosed(): boolean;
|
|
20
|
+
updateText(text: string): Promise<void>;
|
|
21
|
+
applyChanges(changes: readonly ContentChange[]): Promise<void>;
|
|
22
|
+
save(): Promise<void>;
|
|
23
|
+
completion(position: Position, context?: CompletionContext, options?: DocumentRequestOptions): Promise<CompletionResult>;
|
|
24
|
+
resolveCompletion(item: CompletionItem, options?: DocumentRequestOptions): Promise<CompletionItem>;
|
|
25
|
+
hover(position: Position, options?: DocumentRequestOptions): Promise<Hover | null>;
|
|
26
|
+
signatureHelp(position: Position, context?: SignatureHelpContext, options?: DocumentRequestOptions): Promise<SignatureHelp | null>;
|
|
27
|
+
definition(position: Position, options?: DocumentRequestOptions): Promise<NavigationResult>;
|
|
28
|
+
declaration(position: Position, options?: DocumentRequestOptions): Promise<NavigationResult>;
|
|
29
|
+
typeDefinition(position: Position, options?: DocumentRequestOptions): Promise<NavigationResult>;
|
|
30
|
+
implementation(position: Position, options?: DocumentRequestOptions): Promise<NavigationResult>;
|
|
31
|
+
references(position: Position, includeDeclaration?: boolean, options?: DocumentRequestOptions): Promise<Location[] | null>;
|
|
32
|
+
documentHighlights(position: Position, options?: DocumentRequestOptions): Promise<DocumentHighlight[] | null>;
|
|
33
|
+
documentSymbols(options?: DocumentRequestOptions): Promise<DocumentSymbolResult>;
|
|
34
|
+
prepareRename(position: Position, options?: DocumentRequestOptions): Promise<PrepareRenameResult>;
|
|
35
|
+
rename(position: Position, newName: string, options?: DocumentRequestOptions): Promise<WorkspaceEdit | null>;
|
|
36
|
+
codeActions(range: Range, context?: CodeActionContext, options?: DocumentRequestOptions): Promise<(CodeAction | Command)[] | null>;
|
|
37
|
+
resolveCodeAction(action: CodeAction, options?: DocumentRequestOptions): Promise<CodeAction>;
|
|
38
|
+
codeLenses(options?: DocumentRequestOptions): Promise<CodeLens[] | null>;
|
|
39
|
+
resolveCodeLens(lens: CodeLens, options?: DocumentRequestOptions): Promise<CodeLens>;
|
|
40
|
+
formatting(formatting: FormattingOptions, options?: DocumentRequestOptions): Promise<TextEdit[] | null>;
|
|
41
|
+
rangeFormatting(range: Range, formatting: FormattingOptions, options?: DocumentRequestOptions): Promise<TextEdit[] | null>;
|
|
42
|
+
foldingRanges(options?: DocumentRequestOptions): Promise<FoldingRange[] | null>;
|
|
43
|
+
selectionRanges(positions: readonly Position[], options?: DocumentRequestOptions): Promise<SelectionRange[] | null>;
|
|
44
|
+
semanticTokens(options?: DocumentRequestOptions): Promise<SemanticTokens | null>;
|
|
45
|
+
semanticTokensDelta(previousResultId: string, options?: DocumentRequestOptions): Promise<SemanticTokens | SemanticTokensDelta | null>;
|
|
46
|
+
semanticTokensRange(range: Range, options?: DocumentRequestOptions): Promise<SemanticTokens | null>;
|
|
47
|
+
inlayHints(range: Range, options?: DocumentRequestOptions): Promise<InlayHint[] | null>;
|
|
48
|
+
resolveInlayHint(hint: InlayHint, options?: DocumentRequestOptions): Promise<InlayHint>;
|
|
49
|
+
diagnostics(previousResultId?: string, identifier?: string, options?: DocumentRequestOptions): Promise<DocumentDiagnosticReport>;
|
|
50
|
+
request<T = unknown>(method: string, params: object, options?: DocumentRequestOptions): Promise<T>;
|
|
51
|
+
close(): Promise<void>;
|
|
52
|
+
disconnect(): void;
|
|
53
|
+
private feature;
|
|
54
|
+
private resolve;
|
|
55
|
+
private perform;
|
|
56
|
+
private remember;
|
|
57
|
+
private assertOpen;
|
|
58
|
+
private assertCurrent;
|
|
59
|
+
private cancelRequests;
|
|
60
|
+
}
|