@adecore/lsp 0.39.0 → 0.40.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/dist/completion-defaults.js +2 -4
- package/dist/edits.d.ts +4 -0
- package/dist/edits.js +8 -5
- package/dist/file-operations.js +6 -3
- package/dist/session.d.ts +2 -0
- package/dist/session.js +14 -18
- package/dist/test-transport.js +1 -1
- package/dist/text-mirror.js +2 -5
- package/dist/transport.d.ts +1 -0
- package/dist/transport.js +7 -4
- package/dist/uris.d.ts +1 -0
- package/dist/uris.js +5 -2
- package/dist/watched-files.js +2 -2
- package/dist/workspace-operations.js +2 -3
- package/package.json +2 -2
- package/src/completion-defaults.ts +24 -4
- package/src/connection.ts +45 -0
- package/src/document.ts +39 -0
- package/src/edits.ts +58 -5
- package/src/file-operations.ts +13 -3
- package/src/glob.ts +6 -0
- package/src/session.ts +84 -18
- package/src/test-transport.ts +6 -1
- package/src/testing.ts +11 -0
- package/src/text-mirror.ts +27 -6
- package/src/transport.ts +58 -4
- package/src/uris.ts +17 -2
- package/src/vue.ts +15 -0
- package/src/watched-files.ts +14 -2
- package/src/workspace-operations.ts +51 -3
package/src/transport.ts
CHANGED
|
@@ -17,9 +17,11 @@ class TransportEvents {
|
|
|
17
17
|
|
|
18
18
|
onMessage(listener: (message: unknown) => void): Disposable {
|
|
19
19
|
this.messages.add(listener);
|
|
20
|
+
|
|
20
21
|
for (const message of this.backlog.splice(0)) {
|
|
21
22
|
listener(message);
|
|
22
23
|
}
|
|
24
|
+
|
|
23
25
|
return {
|
|
24
26
|
dispose: () => {
|
|
25
27
|
this.messages.delete(listener);
|
|
@@ -29,9 +31,11 @@ class TransportEvents {
|
|
|
29
31
|
|
|
30
32
|
onClose(listener: (error?: Error) => void): Disposable {
|
|
31
33
|
this.closes.add(listener);
|
|
34
|
+
|
|
32
35
|
if (this.closed) {
|
|
33
36
|
listener(this.error);
|
|
34
37
|
}
|
|
38
|
+
|
|
35
39
|
return {
|
|
36
40
|
dispose: () => {
|
|
37
41
|
this.closes.delete(listener);
|
|
@@ -43,10 +47,13 @@ class TransportEvents {
|
|
|
43
47
|
if (this.closed) {
|
|
44
48
|
return;
|
|
45
49
|
}
|
|
50
|
+
|
|
46
51
|
if (this.messages.size === 0) {
|
|
47
52
|
this.backlog.push(message);
|
|
53
|
+
|
|
48
54
|
return;
|
|
49
55
|
}
|
|
56
|
+
|
|
50
57
|
for (const listener of this.messages) {
|
|
51
58
|
listener(message);
|
|
52
59
|
}
|
|
@@ -56,12 +63,15 @@ class TransportEvents {
|
|
|
56
63
|
if (this.closed) {
|
|
57
64
|
return;
|
|
58
65
|
}
|
|
66
|
+
|
|
59
67
|
this.closed = true;
|
|
60
68
|
this.error = error;
|
|
61
69
|
this.backlog = [];
|
|
70
|
+
|
|
62
71
|
for (const listener of this.closes) {
|
|
63
72
|
listener(error);
|
|
64
73
|
}
|
|
74
|
+
|
|
65
75
|
this.closes.clear();
|
|
66
76
|
this.messages.clear();
|
|
67
77
|
}
|
|
@@ -71,12 +81,15 @@ function concat(chunks: readonly Uint8Array[], size: number): Uint8Array {
|
|
|
71
81
|
if (chunks.length === 1) {
|
|
72
82
|
return chunks[0];
|
|
73
83
|
}
|
|
84
|
+
|
|
74
85
|
const joined = new Uint8Array(size);
|
|
75
86
|
let offset = 0;
|
|
87
|
+
|
|
76
88
|
for (const chunk of chunks) {
|
|
77
89
|
joined.set(chunk, offset);
|
|
78
90
|
offset += chunk.length;
|
|
79
91
|
}
|
|
92
|
+
|
|
80
93
|
return joined;
|
|
81
94
|
}
|
|
82
95
|
|
|
@@ -89,12 +102,14 @@ function indexOfHeaderEnd(bytes: Uint8Array): number {
|
|
|
89
102
|
return i;
|
|
90
103
|
}
|
|
91
104
|
}
|
|
105
|
+
|
|
92
106
|
return -1;
|
|
93
107
|
}
|
|
94
108
|
|
|
95
109
|
/* Reads `Content-Length` framed messages, the way a language server speaks over stdio. */
|
|
96
110
|
export class ContentLengthDecoder {
|
|
97
111
|
private readonly maxMessageBytes: number;
|
|
112
|
+
private readonly utf8 = new TextDecoder();
|
|
98
113
|
private chunks: Uint8Array[] = [];
|
|
99
114
|
private size = 0;
|
|
100
115
|
private length: number | null = null;
|
|
@@ -108,49 +123,64 @@ export class ContentLengthDecoder {
|
|
|
108
123
|
this.chunks.push(chunk);
|
|
109
124
|
this.size += chunk.length;
|
|
110
125
|
const messages: unknown[] = [];
|
|
126
|
+
|
|
111
127
|
while (true) {
|
|
112
128
|
if (this.length === null) {
|
|
113
129
|
const buffer = concat(this.chunks, this.size);
|
|
114
130
|
this.chunks = [buffer];
|
|
115
131
|
const boundary = indexOfHeaderEnd(buffer);
|
|
132
|
+
|
|
116
133
|
if (boundary < 0) {
|
|
117
134
|
if (buffer.length > MAX_HEADER_BYTES) {
|
|
118
135
|
throw new Error('LSP header is too large');
|
|
119
136
|
}
|
|
137
|
+
|
|
120
138
|
break;
|
|
121
139
|
}
|
|
122
|
-
|
|
140
|
+
|
|
141
|
+
const header = this.utf8.decode(buffer.subarray(0, boundary));
|
|
123
142
|
const match = /^Content-Length:\s*(\d+)\s*$/im.exec(header);
|
|
143
|
+
|
|
124
144
|
if (!match || boundary > MAX_HEADER_BYTES) {
|
|
125
145
|
throw new Error('Invalid LSP Content-Length header');
|
|
126
146
|
}
|
|
147
|
+
|
|
127
148
|
const length = Number(match[1]);
|
|
149
|
+
|
|
128
150
|
if (!length || length > this.maxMessageBytes) {
|
|
129
151
|
throw new Error('LSP message is too large');
|
|
130
152
|
}
|
|
153
|
+
|
|
131
154
|
this.length = length;
|
|
132
155
|
this.chunks = [buffer.subarray(boundary + 4)];
|
|
133
156
|
this.size = buffer.length - boundary - 4;
|
|
134
157
|
}
|
|
158
|
+
|
|
135
159
|
if (this.size < this.length) {
|
|
136
160
|
break;
|
|
137
161
|
}
|
|
162
|
+
|
|
138
163
|
const buffer = concat(this.chunks, this.size);
|
|
139
|
-
messages.push(JSON.parse(
|
|
164
|
+
messages.push(JSON.parse(this.utf8.decode(buffer.subarray(0, this.length))));
|
|
140
165
|
this.chunks = [buffer.subarray(this.length)];
|
|
141
166
|
this.size = buffer.length - this.length;
|
|
142
167
|
this.length = null;
|
|
143
168
|
}
|
|
169
|
+
|
|
144
170
|
return messages;
|
|
145
171
|
}
|
|
146
172
|
}
|
|
147
173
|
|
|
174
|
+
let utf8: TextEncoder | undefined;
|
|
175
|
+
|
|
148
176
|
export function encodeMessage(message: RpcMessage): Uint8Array {
|
|
149
|
-
|
|
150
|
-
const
|
|
177
|
+
utf8 ??= new TextEncoder();
|
|
178
|
+
const body = utf8.encode(JSON.stringify(message));
|
|
179
|
+
const header = utf8.encode(`Content-Length: ${body.length}\r\n\r\n`);
|
|
151
180
|
const framed = new Uint8Array(header.length + body.length);
|
|
152
181
|
framed.set(header, 0);
|
|
153
182
|
framed.set(body, header.length);
|
|
183
|
+
|
|
154
184
|
return framed;
|
|
155
185
|
}
|
|
156
186
|
|
|
@@ -166,6 +196,7 @@ export interface ByteStream {
|
|
|
166
196
|
export function createStreamTransport(stream: ByteStream, options: { maxMessageBytes?: number } = {}): LspTransport {
|
|
167
197
|
const events = new TransportEvents();
|
|
168
198
|
const decoder = new ContentLengthDecoder(options.maxMessageBytes);
|
|
199
|
+
|
|
169
200
|
stream.onData((chunk) => {
|
|
170
201
|
try {
|
|
171
202
|
for (const message of decoder.accept(chunk)) {
|
|
@@ -176,7 +207,9 @@ export function createStreamTransport(stream: ByteStream, options: { maxMessageB
|
|
|
176
207
|
void Promise.resolve(stream.close()).catch(() => undefined);
|
|
177
208
|
}
|
|
178
209
|
});
|
|
210
|
+
|
|
179
211
|
stream.onClose((error) => events.finish(error));
|
|
212
|
+
|
|
180
213
|
return {
|
|
181
214
|
onMessage: (listener) => events.onMessage(listener),
|
|
182
215
|
onClose: (listener) => events.onClose(listener),
|
|
@@ -184,6 +217,7 @@ export function createStreamTransport(stream: ByteStream, options: { maxMessageB
|
|
|
184
217
|
if (events.closed) {
|
|
185
218
|
throw new Error('LSP stream is closed');
|
|
186
219
|
}
|
|
220
|
+
|
|
187
221
|
await stream.write(encodeMessage(message));
|
|
188
222
|
},
|
|
189
223
|
async close() {
|
|
@@ -197,6 +231,7 @@ export function createStreamTransport(stream: ByteStream, options: { maxMessageB
|
|
|
197
231
|
export function createMemoryTransportPair(): [LspTransport, LspTransport] {
|
|
198
232
|
const left = new TransportEvents();
|
|
199
233
|
const right = new TransportEvents();
|
|
234
|
+
|
|
200
235
|
const end = (inbox: TransportEvents, outbox: TransportEvents): LspTransport => ({
|
|
201
236
|
onMessage: (listener) => inbox.onMessage(listener),
|
|
202
237
|
onClose: (listener) => inbox.onClose(listener),
|
|
@@ -204,6 +239,7 @@ export function createMemoryTransportPair(): [LspTransport, LspTransport] {
|
|
|
204
239
|
if (inbox.closed || outbox.closed) {
|
|
205
240
|
throw new Error('LSP transport is closed');
|
|
206
241
|
}
|
|
242
|
+
|
|
207
243
|
const copy: unknown = JSON.parse(JSON.stringify(message));
|
|
208
244
|
queueMicrotask(() => outbox.receive(copy));
|
|
209
245
|
},
|
|
@@ -212,6 +248,7 @@ export function createMemoryTransportPair(): [LspTransport, LspTransport] {
|
|
|
212
248
|
outbox.finish();
|
|
213
249
|
}
|
|
214
250
|
});
|
|
251
|
+
|
|
215
252
|
return [end(left, right), end(right, left)];
|
|
216
253
|
}
|
|
217
254
|
|
|
@@ -232,24 +269,30 @@ export async function connectWebSocket(url: string, options: WebSocketTransportO
|
|
|
232
269
|
if (options.signal?.aborted) {
|
|
233
270
|
throw new Error('WebSocket connection cancelled');
|
|
234
271
|
}
|
|
272
|
+
|
|
235
273
|
const timeoutMs = options.timeoutMs ?? 10_000;
|
|
236
274
|
const socket = options.createWebSocket ? options.createWebSocket(url, options.protocols) : new WebSocket(url, options.protocols);
|
|
237
275
|
const events = new TransportEvents();
|
|
276
|
+
|
|
238
277
|
socket.addEventListener('message', (event) => {
|
|
239
278
|
try {
|
|
240
279
|
if (typeof event.data !== 'string') {
|
|
241
280
|
throw new Error('LSP WebSocket expects JSON text frames');
|
|
242
281
|
}
|
|
282
|
+
|
|
243
283
|
events.receive(JSON.parse(event.data));
|
|
244
284
|
} catch (error) {
|
|
245
285
|
events.finish(asError(error));
|
|
246
286
|
socket.close(1003, 'Invalid JSON message');
|
|
247
287
|
}
|
|
248
288
|
});
|
|
289
|
+
|
|
249
290
|
socket.addEventListener('close', (event) => events.finish(event.code === 1000 ? undefined : new Error(`LSP WebSocket closed (${event.code})`)));
|
|
250
291
|
socket.addEventListener('error', () => events.finish(new Error('LSP WebSocket failed')));
|
|
292
|
+
|
|
251
293
|
await new Promise<void>((resolve, reject) => {
|
|
252
294
|
const timer = setTimeout(() => fail(new Error('LSP WebSocket connection timed out')), timeoutMs);
|
|
295
|
+
|
|
253
296
|
const cleanup = (): void => {
|
|
254
297
|
clearTimeout(timer);
|
|
255
298
|
socket.removeEventListener('open', open);
|
|
@@ -257,37 +300,48 @@ export async function connectWebSocket(url: string, options: WebSocketTransportO
|
|
|
257
300
|
socket.removeEventListener('close', closed);
|
|
258
301
|
options.signal?.removeEventListener('abort', abort);
|
|
259
302
|
};
|
|
303
|
+
|
|
260
304
|
const fail = (reason: Error): void => {
|
|
261
305
|
cleanup();
|
|
262
306
|
socket.close();
|
|
263
307
|
reject(reason);
|
|
264
308
|
};
|
|
309
|
+
|
|
265
310
|
const open = (): void => {
|
|
266
311
|
cleanup();
|
|
267
312
|
resolve();
|
|
268
313
|
};
|
|
314
|
+
|
|
269
315
|
const failed = (): void => fail(new Error('LSP WebSocket connection failed'));
|
|
316
|
+
|
|
270
317
|
const closed = (): void => fail(new Error('LSP WebSocket closed before opening'));
|
|
318
|
+
|
|
271
319
|
const abort = (): void => fail(new Error('LSP WebSocket connection cancelled'));
|
|
320
|
+
|
|
272
321
|
socket.addEventListener('open', open, { once: true });
|
|
273
322
|
socket.addEventListener('error', failed, { once: true });
|
|
274
323
|
socket.addEventListener('close', closed, { once: true });
|
|
275
324
|
options.signal?.addEventListener('abort', abort, { once: true });
|
|
276
325
|
});
|
|
326
|
+
|
|
277
327
|
return {
|
|
278
328
|
onMessage: (listener) => events.onMessage(listener),
|
|
279
329
|
onClose: (listener) => events.onClose(listener),
|
|
280
330
|
async send(message) {
|
|
281
331
|
const deadline = Date.now() + timeoutMs;
|
|
332
|
+
|
|
282
333
|
while (!events.closed && socket.readyState === 1 && socket.bufferedAmount > MAX_SOCKET_BUFFER_BYTES) {
|
|
283
334
|
if (Date.now() >= deadline) {
|
|
284
335
|
throw new Error('LSP WebSocket send buffer is full');
|
|
285
336
|
}
|
|
337
|
+
|
|
286
338
|
await new Promise((resolve) => setTimeout(resolve, DRAIN_POLL_MS));
|
|
287
339
|
}
|
|
340
|
+
|
|
288
341
|
if (events.closed || socket.readyState !== 1) {
|
|
289
342
|
throw new Error('LSP WebSocket is closed');
|
|
290
343
|
}
|
|
344
|
+
|
|
291
345
|
socket.send(JSON.stringify(message));
|
|
292
346
|
},
|
|
293
347
|
close() {
|
package/src/uris.ts
CHANGED
|
@@ -2,17 +2,25 @@ function isWindowsDrive(segment: string): boolean {
|
|
|
2
2
|
return /^[A-Za-z]:$/.test(segment);
|
|
3
3
|
}
|
|
4
4
|
|
|
5
|
+
/* A path with a drive or a UNC share. A backslash separates only in such a path; in a POSIX one it is part of a name. */
|
|
6
|
+
export function isWindowsPath(path: string): boolean {
|
|
7
|
+
return /^[A-Za-z]:[\\/]/.test(path) || path.startsWith('\\\\');
|
|
8
|
+
}
|
|
9
|
+
|
|
5
10
|
/* An absolute path, POSIX, with a Windows drive or UNC, as the `file:` URI a language server names it by. */
|
|
6
11
|
export function pathToFileUri(path: string): string {
|
|
7
|
-
|
|
8
|
-
const windows = /^[A-Za-z]:[\\/]/.test(path) || path.startsWith('\\\\');
|
|
12
|
+
const windows = isWindowsPath(path);
|
|
9
13
|
const normalized = windows ? path.replaceAll('\\', '/') : path;
|
|
14
|
+
|
|
10
15
|
if (windows && normalized.startsWith('//')) {
|
|
11
16
|
const [host = '', ...segments] = normalized.slice(2).split('/');
|
|
17
|
+
|
|
12
18
|
return `file://${encodeURIComponent(host)}/${segments.map(encodeURIComponent).join('/')}`;
|
|
13
19
|
}
|
|
20
|
+
|
|
14
21
|
const rooted = normalized.startsWith('/') ? normalized : `/${normalized}`;
|
|
15
22
|
const segments = rooted.split('/').map((segment, index) => (index === 1 && isWindowsDrive(segment) ? segment : encodeURIComponent(segment)));
|
|
23
|
+
|
|
16
24
|
return `file://${segments.join('/')}`;
|
|
17
25
|
}
|
|
18
26
|
|
|
@@ -20,22 +28,27 @@ export function pathToFileUri(path: string): string {
|
|
|
20
28
|
export function fileUriToPath(uri: string): string | null {
|
|
21
29
|
let url: URL;
|
|
22
30
|
let path: string;
|
|
31
|
+
|
|
23
32
|
try {
|
|
24
33
|
url = new URL(uri);
|
|
25
34
|
path = decodeURIComponent(url.pathname);
|
|
26
35
|
} catch {
|
|
27
36
|
return null;
|
|
28
37
|
}
|
|
38
|
+
|
|
29
39
|
if (url.protocol !== 'file:') {
|
|
30
40
|
return null;
|
|
31
41
|
}
|
|
42
|
+
|
|
32
43
|
// A server in the URI names a UNC share, which only Windows reads as a path.
|
|
33
44
|
if (url.hostname !== '' && url.hostname !== 'localhost') {
|
|
34
45
|
return `\\\\${url.hostname}${path.replaceAll('/', '\\')}`;
|
|
35
46
|
}
|
|
47
|
+
|
|
36
48
|
if (/^\/[A-Za-z]:/.test(path)) {
|
|
37
49
|
return path.slice(1).replaceAll('/', '\\');
|
|
38
50
|
}
|
|
51
|
+
|
|
39
52
|
return path;
|
|
40
53
|
}
|
|
41
54
|
|
|
@@ -45,8 +58,10 @@ export function fileUriToPath(uri: string): string | null {
|
|
|
45
58
|
*/
|
|
46
59
|
export function fileUriKey(uri: string): string {
|
|
47
60
|
const path = fileUriToPath(uri);
|
|
61
|
+
|
|
48
62
|
if (path === null || !/^[A-Za-z]:\\/.test(path)) {
|
|
49
63
|
return uri;
|
|
50
64
|
}
|
|
65
|
+
|
|
51
66
|
return pathToFileUri(`${path[0]!.toUpperCase()}${path.slice(1)}`);
|
|
52
67
|
}
|
package/src/vue.ts
CHANGED
|
@@ -10,30 +10,37 @@ import type { LspSession } from './session.ts';
|
|
|
10
10
|
*/
|
|
11
11
|
export function bridgeVueTypeScript(vue: LspSession, typescript: LspSession): Disposable {
|
|
12
12
|
let disposed = false;
|
|
13
|
+
|
|
13
14
|
const subscription = vue.onNotification('tsserver/request', async (params) => {
|
|
14
15
|
// vscode-jsonrpc encodes this single positional argument as an outer array.
|
|
15
16
|
const positional = Array.isArray(params) && params.length === 1 && Array.isArray(params[0]);
|
|
16
17
|
const tuple = positional ? params[0] : params;
|
|
18
|
+
|
|
17
19
|
if (!Array.isArray(tuple) || tuple.length !== 3) {
|
|
18
20
|
throw new Error('Invalid Vue tsserver/request notification');
|
|
19
21
|
}
|
|
22
|
+
|
|
20
23
|
const [id, command, args] = tuple as [number, string, unknown];
|
|
21
24
|
let body: unknown = null;
|
|
25
|
+
|
|
22
26
|
try {
|
|
23
27
|
const response = await typescript.executeCommand<{ body?: unknown }>('typescript.tsserverRequest', [
|
|
24
28
|
command,
|
|
25
29
|
args,
|
|
26
30
|
{ isAsync: false, lowPriority: true }
|
|
27
31
|
]);
|
|
32
|
+
|
|
28
33
|
body = response?.body ?? null;
|
|
29
34
|
} catch (error) {
|
|
30
35
|
vue.connection.reportError(error);
|
|
31
36
|
}
|
|
37
|
+
|
|
32
38
|
// Vue keeps the request pending until it gets an answer, a null one included.
|
|
33
39
|
if (!disposed && !vue.connection.isClosed) {
|
|
34
40
|
await vue.connection.notify('tsserver/response', positional ? [[id, body]] : [id, body]);
|
|
35
41
|
}
|
|
36
42
|
});
|
|
43
|
+
|
|
37
44
|
return {
|
|
38
45
|
dispose() {
|
|
39
46
|
disposed = true;
|
|
@@ -47,17 +54,21 @@ export type VueServer = 'vue' | 'typescript';
|
|
|
47
54
|
/* A `<script>` body, or the expression of an interpolation or a `v-` / `:` / `@` attribute: the places TypeScript knows more than Vue does. */
|
|
48
55
|
export function isVueExpression(text: string, offset: number): boolean {
|
|
49
56
|
const script = /<script\b[^>]*>[\s\S]*?<\/script\s*>/gi;
|
|
57
|
+
|
|
50
58
|
for (let match = script.exec(text); match; match = script.exec(text)) {
|
|
51
59
|
if (offset > match.index + match[0].indexOf('>') && offset < match.index + match[0].lastIndexOf('</')) {
|
|
52
60
|
return true;
|
|
53
61
|
}
|
|
54
62
|
}
|
|
63
|
+
|
|
55
64
|
const expressions = /\{\{[\s\S]*?\}\}|(?:[:@][\w.:-]+|v-[\w.:-]+)\s*=\s*(?:"[^"]*"|'[^']*')/g;
|
|
65
|
+
|
|
56
66
|
for (let match = expressions.exec(text); match; match = expressions.exec(text)) {
|
|
57
67
|
if (offset >= match.index && offset <= match.index + match[0].length) {
|
|
58
68
|
return true;
|
|
59
69
|
}
|
|
60
70
|
}
|
|
71
|
+
|
|
61
72
|
return false;
|
|
62
73
|
}
|
|
63
74
|
|
|
@@ -65,15 +76,18 @@ function paramsOffset(text: string, params: unknown): number | null {
|
|
|
65
76
|
if (!params || typeof params !== 'object') {
|
|
66
77
|
return null;
|
|
67
78
|
}
|
|
79
|
+
|
|
68
80
|
const position =
|
|
69
81
|
'position' in params
|
|
70
82
|
? params.position
|
|
71
83
|
: 'range' in params && params.range && typeof params.range === 'object' && 'start' in params.range
|
|
72
84
|
? params.range.start
|
|
73
85
|
: null;
|
|
86
|
+
|
|
74
87
|
if (!position || typeof position !== 'object') {
|
|
75
88
|
return null;
|
|
76
89
|
}
|
|
90
|
+
|
|
77
91
|
try {
|
|
78
92
|
return offsetAt(text, position as { line: number; character: number });
|
|
79
93
|
} catch {
|
|
@@ -89,5 +103,6 @@ function paramsOffset(text: string, params: unknown): number | null {
|
|
|
89
103
|
export function vueServerOrder(method: string, text: string, params: unknown): [VueServer, VueServer] {
|
|
90
104
|
const offset = paramsOffset(text, params);
|
|
91
105
|
const typescriptFirst = method === 'textDocument/inlayHint' || (offset !== null && isVueExpression(text, offset));
|
|
106
|
+
|
|
92
107
|
return typescriptFirst ? ['typescript', 'vue'] : ['vue', 'typescript'];
|
|
93
108
|
}
|
package/src/watched-files.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { globMatch } from './glob.ts';
|
|
2
2
|
import type { FileSystemWatcher } from './protocol.ts';
|
|
3
|
-
import { fileUriToPath } from './uris.ts';
|
|
3
|
+
import { fileUriToPath, isWindowsPath } from './uris.ts';
|
|
4
4
|
|
|
5
5
|
/* A change of a file as the protocol numbers it: created, changed, deleted. */
|
|
6
6
|
export type FileChangeType = 1 | 2 | 3;
|
|
@@ -12,25 +12,31 @@ const SPARING_FOLDERS = ['node_modules', 'vendor', '.git'];
|
|
|
12
12
|
|
|
13
13
|
/* A Windows path with forward slashes and its drive in capitals, so it compares with a pattern and a root however it was written. A POSIX path is left alone, a backslash being part of a name there. */
|
|
14
14
|
function slashed(path: string): string {
|
|
15
|
-
if (
|
|
15
|
+
if (!isWindowsPath(path)) {
|
|
16
16
|
return path;
|
|
17
17
|
}
|
|
18
|
+
|
|
18
19
|
const forward = path.replaceAll('\\', '/');
|
|
20
|
+
|
|
19
21
|
return /^[a-z]:/.test(forward) ? `${forward[0]!.toUpperCase()}${forward.slice(1)}` : forward;
|
|
20
22
|
}
|
|
21
23
|
|
|
22
24
|
function relativeTo(root: string, path: string): string | null {
|
|
23
25
|
const prefix = root.endsWith('/') ? root : `${root}/`;
|
|
26
|
+
|
|
24
27
|
return path.startsWith(prefix) ? path.slice(prefix.length) : null;
|
|
25
28
|
}
|
|
26
29
|
|
|
27
30
|
/* A pattern with the folder it is relative to, or null for a relative pattern whose base is no folder of this machine. */
|
|
28
31
|
function patternOf(watcher: FileSystemWatcher): { base: string | undefined; pattern: string } | null {
|
|
29
32
|
const { globPattern } = watcher;
|
|
33
|
+
|
|
30
34
|
if (typeof globPattern === 'string') {
|
|
31
35
|
return { base: undefined, pattern: globPattern };
|
|
32
36
|
}
|
|
37
|
+
|
|
33
38
|
const base = fileUriToPath(typeof globPattern.baseUri === 'string' ? globPattern.baseUri : globPattern.baseUri.uri);
|
|
39
|
+
|
|
34
40
|
return base === null ? null : { base: slashed(base), pattern: globPattern.pattern };
|
|
35
41
|
}
|
|
36
42
|
|
|
@@ -43,19 +49,25 @@ export function watchesFile(watchers: readonly FileSystemWatcher[], root: string
|
|
|
43
49
|
const path = slashed(changed);
|
|
44
50
|
const inProject = relativeTo(slashed(root), path);
|
|
45
51
|
const segments = (inProject ?? path).split('/');
|
|
52
|
+
|
|
46
53
|
return watchers.some((watcher) => {
|
|
47
54
|
if (((watcher.kind ?? EVERY_CHANGE) & (1 << (type - 1))) === 0) {
|
|
48
55
|
return false;
|
|
49
56
|
}
|
|
57
|
+
|
|
50
58
|
const parts = patternOf(watcher);
|
|
59
|
+
|
|
51
60
|
if (parts === null) {
|
|
52
61
|
return false;
|
|
53
62
|
}
|
|
63
|
+
|
|
54
64
|
const { base, pattern } = parts;
|
|
55
65
|
const subject = pattern.startsWith('/') || /^[A-Za-z]:\//.test(pattern) ? path : base === undefined ? inProject : relativeTo(base, path);
|
|
66
|
+
|
|
56
67
|
if (subject === null || !globMatch(subject, pattern)) {
|
|
57
68
|
return false;
|
|
58
69
|
}
|
|
70
|
+
|
|
59
71
|
return SPARING_FOLDERS.every((folder) => !segments.includes(folder) || pattern.includes(folder));
|
|
60
72
|
});
|
|
61
73
|
}
|