tailwint 1.1.6 → 1.1.8
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 +36 -32
- package/dist/edits.js +2 -109
- package/dist/index.js +2 -270
- package/dist/lsp.js +7 -487
- package/dist/prescan.js +1 -79
- package/dist/ui.js +3 -203
- package/package.json +4 -3
- package/dist/edits.test.d.ts +0 -4
- package/dist/edits.test.js +0 -410
package/dist/lsp.js
CHANGED
|
@@ -1,487 +1,7 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
let workspaceRoot = "";
|
|
9
|
-
let vscodeSettings = null;
|
|
10
|
-
/** Load .vscode/settings.json once, cache the result. */
|
|
11
|
-
function loadVscodeSettings() {
|
|
12
|
-
if (vscodeSettings !== null)
|
|
13
|
-
return vscodeSettings;
|
|
14
|
-
const settingsPath = resolve(workspaceRoot, ".vscode/settings.json");
|
|
15
|
-
if (!existsSync(settingsPath)) {
|
|
16
|
-
vscodeSettings = {};
|
|
17
|
-
return vscodeSettings;
|
|
18
|
-
}
|
|
19
|
-
try {
|
|
20
|
-
// Strip single-line comments (// ...) and trailing commas for JSON compat
|
|
21
|
-
const raw = readFileSync(settingsPath, "utf-8")
|
|
22
|
-
.replace(/\/\/[^\n]*/g, "")
|
|
23
|
-
.replace(/,\s*([\]}])/g, "$1");
|
|
24
|
-
vscodeSettings = JSON.parse(raw);
|
|
25
|
-
}
|
|
26
|
-
catch {
|
|
27
|
-
vscodeSettings = {};
|
|
28
|
-
}
|
|
29
|
-
return vscodeSettings;
|
|
30
|
-
}
|
|
31
|
-
/**
|
|
32
|
-
* Extract a section from flat VS Code settings into a nested object.
|
|
33
|
-
* e.g. section "tailwindCSS" turns { "tailwindCSS.lint.cssConflict": "error" }
|
|
34
|
-
* into { lint: { cssConflict: "error" } }
|
|
35
|
-
*/
|
|
36
|
-
function getSettingsSection(section) {
|
|
37
|
-
const settings = loadVscodeSettings();
|
|
38
|
-
const prefix = section + ".";
|
|
39
|
-
const result = {};
|
|
40
|
-
for (const [key, value] of Object.entries(settings)) {
|
|
41
|
-
if (!key.startsWith(prefix))
|
|
42
|
-
continue;
|
|
43
|
-
const path = key.slice(prefix.length).split(".");
|
|
44
|
-
let target = result;
|
|
45
|
-
for (let i = 0; i < path.length - 1; i++) {
|
|
46
|
-
if (!(path[i] in target) || typeof target[path[i]] !== "object") {
|
|
47
|
-
target[path[i]] = {};
|
|
48
|
-
}
|
|
49
|
-
target = target[path[i]];
|
|
50
|
-
}
|
|
51
|
-
target[path[path.length - 1]] = value;
|
|
52
|
-
}
|
|
53
|
-
return result;
|
|
54
|
-
}
|
|
55
|
-
let server;
|
|
56
|
-
let serverDead = false;
|
|
57
|
-
let msgId = 0;
|
|
58
|
-
const chunks = [];
|
|
59
|
-
let chunksLen = 0;
|
|
60
|
-
const pending = new Map();
|
|
61
|
-
export const diagnosticsReceived = new Map();
|
|
62
|
-
export let projectReady = false;
|
|
63
|
-
// ---------------------------------------------------------------------------
|
|
64
|
-
// Project-aware wait state
|
|
65
|
-
// ---------------------------------------------------------------------------
|
|
66
|
-
/** Tracking for projectInitialized events */
|
|
67
|
-
export let projectInitCount = 0;
|
|
68
|
-
export let settledProjects = 0;
|
|
69
|
-
export let brokenProjects = 0;
|
|
70
|
-
let lastInitMs = 0;
|
|
71
|
-
let inBrokenSequence = false;
|
|
72
|
-
let awaitingFirstDiag = false;
|
|
73
|
-
let currentProjectDiagCount = 0;
|
|
74
|
-
export const warnings = [];
|
|
75
|
-
/** Internal waiter state */
|
|
76
|
-
let projectWaitResolve = null;
|
|
77
|
-
let diagDebounceTimer = null;
|
|
78
|
-
let projectInitTimer = null;
|
|
79
|
-
let outerTimer = null;
|
|
80
|
-
const diagWaiters = new Map();
|
|
81
|
-
/** Config for the current wait */
|
|
82
|
-
let waitConfig = { predictedRoots: 0, maxProjects: 0, initTimeoutMs: 5000, debounceMs: 500 };
|
|
83
|
-
/** Reset module state between runs (for programmatic multi-run usage). */
|
|
84
|
-
export function resetState() {
|
|
85
|
-
msgId = 0;
|
|
86
|
-
serverDead = false;
|
|
87
|
-
chunks.length = 0;
|
|
88
|
-
chunksLen = 0;
|
|
89
|
-
pending.clear();
|
|
90
|
-
diagnosticsReceived.clear();
|
|
91
|
-
projectReady = false;
|
|
92
|
-
projectInitCount = 0;
|
|
93
|
-
settledProjects = 0;
|
|
94
|
-
brokenProjects = 0;
|
|
95
|
-
lastInitMs = 0;
|
|
96
|
-
inBrokenSequence = false;
|
|
97
|
-
awaitingFirstDiag = false;
|
|
98
|
-
currentProjectDiagCount = 0;
|
|
99
|
-
warnings.length = 0;
|
|
100
|
-
projectWaitResolve = null;
|
|
101
|
-
if (diagDebounceTimer) {
|
|
102
|
-
clearTimeout(diagDebounceTimer);
|
|
103
|
-
diagDebounceTimer = null;
|
|
104
|
-
}
|
|
105
|
-
if (projectInitTimer) {
|
|
106
|
-
clearTimeout(projectInitTimer);
|
|
107
|
-
projectInitTimer = null;
|
|
108
|
-
}
|
|
109
|
-
if (outerTimer) {
|
|
110
|
-
clearTimeout(outerTimer);
|
|
111
|
-
outerTimer = null;
|
|
112
|
-
}
|
|
113
|
-
diagWaiters.clear();
|
|
114
|
-
vscodeSettings = null;
|
|
115
|
-
}
|
|
116
|
-
function cleanupWaitTimers() {
|
|
117
|
-
if (diagDebounceTimer) {
|
|
118
|
-
clearTimeout(diagDebounceTimer);
|
|
119
|
-
diagDebounceTimer = null;
|
|
120
|
-
}
|
|
121
|
-
if (projectInitTimer) {
|
|
122
|
-
clearTimeout(projectInitTimer);
|
|
123
|
-
projectInitTimer = null;
|
|
124
|
-
}
|
|
125
|
-
if (outerTimer) {
|
|
126
|
-
clearTimeout(outerTimer);
|
|
127
|
-
outerTimer = null;
|
|
128
|
-
}
|
|
129
|
-
}
|
|
130
|
-
function finishWait() {
|
|
131
|
-
if (!projectWaitResolve)
|
|
132
|
-
return;
|
|
133
|
-
const resolve = projectWaitResolve;
|
|
134
|
-
projectWaitResolve = null;
|
|
135
|
-
cleanupWaitTimers();
|
|
136
|
-
resolve();
|
|
137
|
-
}
|
|
138
|
-
function isAllResolved() {
|
|
139
|
-
const resolved = settledProjects + brokenProjects;
|
|
140
|
-
return resolved >= waitConfig.maxProjects;
|
|
141
|
-
}
|
|
142
|
-
function startProjectInitTimeout() {
|
|
143
|
-
if (projectInitTimer)
|
|
144
|
-
clearTimeout(projectInitTimer);
|
|
145
|
-
projectInitTimer = setTimeout(() => {
|
|
146
|
-
// Timer fired — either no project init came, or we were waiting for
|
|
147
|
-
// more diagnostics after a single early one. If we got any diagnostics
|
|
148
|
-
// for the current project, settle it before finishing.
|
|
149
|
-
if (currentProjectDiagCount > 0 && !awaitingFirstDiag) {
|
|
150
|
-
settleCurrentProject();
|
|
151
|
-
}
|
|
152
|
-
else {
|
|
153
|
-
finishWait();
|
|
154
|
-
}
|
|
155
|
-
}, waitConfig.initTimeoutMs);
|
|
156
|
-
}
|
|
157
|
-
function onProjectInitialized() {
|
|
158
|
-
projectInitCount++;
|
|
159
|
-
const now = Date.now();
|
|
160
|
-
projectReady = true;
|
|
161
|
-
if (lastInitMs > 0 && (now - lastInitMs) < 500) {
|
|
162
|
-
// Rapid re-init — broken project
|
|
163
|
-
if (!inBrokenSequence) {
|
|
164
|
-
// First rapid init after a healthy one — the previous healthy init was actually broken
|
|
165
|
-
inBrokenSequence = true;
|
|
166
|
-
brokenProjects++;
|
|
167
|
-
warnings.push("A CSS file failed to initialize (likely an @apply referencing an unknown utility). " +
|
|
168
|
-
"That project's files will not receive diagnostics. " +
|
|
169
|
-
"See https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1121");
|
|
170
|
-
// The previous init was counted as starting a healthy project's diagnostic wait.
|
|
171
|
-
// Cancel that wait — this project won't produce diagnostics.
|
|
172
|
-
if (diagDebounceTimer) {
|
|
173
|
-
clearTimeout(diagDebounceTimer);
|
|
174
|
-
diagDebounceTimer = null;
|
|
175
|
-
}
|
|
176
|
-
}
|
|
177
|
-
// Additional rapid inits for the same broken project — just update timestamp
|
|
178
|
-
}
|
|
179
|
-
else {
|
|
180
|
-
// Healthy init — new project starting
|
|
181
|
-
inBrokenSequence = false;
|
|
182
|
-
awaitingFirstDiag = true;
|
|
183
|
-
currentProjectDiagCount = 0;
|
|
184
|
-
// Cancel any pending project-init timeout since we just got a new one
|
|
185
|
-
if (projectInitTimer) {
|
|
186
|
-
clearTimeout(projectInitTimer);
|
|
187
|
-
projectInitTimer = null;
|
|
188
|
-
}
|
|
189
|
-
if (diagDebounceTimer) {
|
|
190
|
-
clearTimeout(diagDebounceTimer);
|
|
191
|
-
diagDebounceTimer = null;
|
|
192
|
-
}
|
|
193
|
-
// Don't start the diagnostic debounce yet — wait for the first diagnostic to arrive.
|
|
194
|
-
// Use the init timeout as the safety net (if no diagnostics arrive at all,
|
|
195
|
-
// this project is effectively broken even though it didn't rapid-fire).
|
|
196
|
-
startProjectInitTimeout();
|
|
197
|
-
}
|
|
198
|
-
lastInitMs = now;
|
|
199
|
-
// Check if broken projects pushed us to completion
|
|
200
|
-
if (isAllResolved()) {
|
|
201
|
-
finishWait();
|
|
202
|
-
}
|
|
203
|
-
}
|
|
204
|
-
function settleCurrentProject() {
|
|
205
|
-
settledProjects++;
|
|
206
|
-
if (isAllResolved()) {
|
|
207
|
-
finishWait();
|
|
208
|
-
}
|
|
209
|
-
else {
|
|
210
|
-
startProjectInitTimeout();
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
function startDiagDebounce() {
|
|
214
|
-
if (diagDebounceTimer)
|
|
215
|
-
clearTimeout(diagDebounceTimer);
|
|
216
|
-
// Cancel the init timeout — we're now in diagnostic-settling mode
|
|
217
|
-
if (projectInitTimer) {
|
|
218
|
-
clearTimeout(projectInitTimer);
|
|
219
|
-
projectInitTimer = null;
|
|
220
|
-
}
|
|
221
|
-
diagDebounceTimer = setTimeout(settleCurrentProject, waitConfig.debounceMs);
|
|
222
|
-
}
|
|
223
|
-
function onDiagnosticReceived() {
|
|
224
|
-
if (!projectWaitResolve)
|
|
225
|
-
return;
|
|
226
|
-
currentProjectDiagCount++;
|
|
227
|
-
if (awaitingFirstDiag) {
|
|
228
|
-
// First diagnostic after a healthy init — don't start the debounce yet.
|
|
229
|
-
// The first diagnostic is often just the CSS entry point, followed by a
|
|
230
|
-
// ~1s pause before the bulk TSX diagnostics arrive. Starting the debounce
|
|
231
|
-
// here would settle too early on large projects.
|
|
232
|
-
awaitingFirstDiag = false;
|
|
233
|
-
}
|
|
234
|
-
else if (currentProjectDiagCount >= 2) {
|
|
235
|
-
// Second diagnostic and beyond — the bulk is flowing, debounce is safe
|
|
236
|
-
startDiagDebounce();
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
/**
|
|
240
|
-
* Wait for all expected projects to be resolved (settled or broken).
|
|
241
|
-
*
|
|
242
|
-
* @param predictedRoots - Number of CSS files predicted to be project roots
|
|
243
|
-
* @param maxProjects - Upper bound (predictedRoots + predictedNonRoots)
|
|
244
|
-
* @param initTimeoutMs - How long to wait for each projectInitialized event
|
|
245
|
-
* @param debounceMs - Silence window to consider diagnostics "settled"
|
|
246
|
-
*/
|
|
247
|
-
export function waitForAllProjects(predictedRoots, maxProjects, initTimeoutMs = 5_000, debounceMs = 500) {
|
|
248
|
-
if (serverDead || maxProjects === 0)
|
|
249
|
-
return Promise.resolve();
|
|
250
|
-
waitConfig = { predictedRoots, maxProjects, initTimeoutMs, debounceMs };
|
|
251
|
-
return new Promise((res) => {
|
|
252
|
-
projectWaitResolve = res;
|
|
253
|
-
// Start waiting for first project init
|
|
254
|
-
startProjectInitTimeout();
|
|
255
|
-
// Hard outer timeout — never wait longer than this
|
|
256
|
-
const outerMs = initTimeoutMs + (maxProjects * 3000) + 5000;
|
|
257
|
-
outerTimer = setTimeout(finishWait, Math.min(outerMs, 30_000));
|
|
258
|
-
});
|
|
259
|
-
}
|
|
260
|
-
/** Returns a promise that resolves when diagnostics are published for a specific URI. */
|
|
261
|
-
export function waitForDiagnostic(uri, timeoutMs = 10_000) {
|
|
262
|
-
if (serverDead)
|
|
263
|
-
return Promise.resolve([]);
|
|
264
|
-
// Clear stale entry so we wait for the server to re-publish
|
|
265
|
-
diagnosticsReceived.delete(uri);
|
|
266
|
-
return new Promise((res) => {
|
|
267
|
-
const timer = setTimeout(() => {
|
|
268
|
-
if (diagWaiters.has(uri)) {
|
|
269
|
-
diagWaiters.delete(uri);
|
|
270
|
-
res([]);
|
|
271
|
-
}
|
|
272
|
-
}, timeoutMs);
|
|
273
|
-
diagWaiters.set(uri, (diags) => { clearTimeout(timer); res(diags); });
|
|
274
|
-
});
|
|
275
|
-
}
|
|
276
|
-
// ---------------------------------------------------------------------------
|
|
277
|
-
// JSON-RPC framing
|
|
278
|
-
// ---------------------------------------------------------------------------
|
|
279
|
-
function encode(obj) {
|
|
280
|
-
const body = JSON.stringify(obj);
|
|
281
|
-
return `Content-Length: ${Buffer.byteLength(body)}\r\n\r\n${body}`;
|
|
282
|
-
}
|
|
283
|
-
function getRawBuf() {
|
|
284
|
-
if (chunks.length === 0)
|
|
285
|
-
return Buffer.alloc(0);
|
|
286
|
-
if (chunks.length === 1)
|
|
287
|
-
return chunks[0];
|
|
288
|
-
const buf = Buffer.concat(chunks, chunksLen);
|
|
289
|
-
chunks.length = 0;
|
|
290
|
-
chunks.push(buf);
|
|
291
|
-
return buf;
|
|
292
|
-
}
|
|
293
|
-
function setRawBuf(buf) {
|
|
294
|
-
chunks.length = 0;
|
|
295
|
-
if (buf.length > 0) {
|
|
296
|
-
chunks.push(buf);
|
|
297
|
-
chunksLen = buf.length;
|
|
298
|
-
}
|
|
299
|
-
else {
|
|
300
|
-
chunksLen = 0;
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
function processMessages() {
|
|
304
|
-
while (true) {
|
|
305
|
-
const rawBuf = getRawBuf();
|
|
306
|
-
if (rawBuf.length === 0)
|
|
307
|
-
break;
|
|
308
|
-
const str = rawBuf.toString("ascii", 0, Math.min(rawBuf.length, 256));
|
|
309
|
-
const headerEnd = str.indexOf("\r\n\r\n");
|
|
310
|
-
if (headerEnd === -1) {
|
|
311
|
-
setRawBuf(rawBuf);
|
|
312
|
-
break;
|
|
313
|
-
}
|
|
314
|
-
const headerBlock = str.slice(0, headerEnd);
|
|
315
|
-
const clMatch = headerBlock.match(/Content-Length:\s*(\d+)/i);
|
|
316
|
-
if (!clMatch) {
|
|
317
|
-
setRawBuf(rawBuf.subarray(headerEnd + 4));
|
|
318
|
-
continue;
|
|
319
|
-
}
|
|
320
|
-
const len = parseInt(clMatch[1], 10);
|
|
321
|
-
const bodyStart = headerEnd + 4;
|
|
322
|
-
if (rawBuf.length < bodyStart + len) {
|
|
323
|
-
setRawBuf(rawBuf);
|
|
324
|
-
break;
|
|
325
|
-
}
|
|
326
|
-
const body = rawBuf.subarray(bodyStart, bodyStart + len).toString("utf-8");
|
|
327
|
-
setRawBuf(rawBuf.subarray(bodyStart + len));
|
|
328
|
-
let msg;
|
|
329
|
-
try {
|
|
330
|
-
msg = JSON.parse(body);
|
|
331
|
-
}
|
|
332
|
-
catch {
|
|
333
|
-
continue;
|
|
334
|
-
}
|
|
335
|
-
if (DEBUG)
|
|
336
|
-
console.error(`<- ${msg.method || `response#${msg.id}`}`);
|
|
337
|
-
// Response to our request
|
|
338
|
-
if (msg.id != null && !msg.method && pending.has(msg.id)) {
|
|
339
|
-
const p = pending.get(msg.id);
|
|
340
|
-
pending.delete(msg.id);
|
|
341
|
-
if (msg.error)
|
|
342
|
-
p.reject(msg.error);
|
|
343
|
-
else
|
|
344
|
-
p.resolve(msg.result);
|
|
345
|
-
continue;
|
|
346
|
-
}
|
|
347
|
-
// Server-initiated requests — must respond
|
|
348
|
-
if (msg.id != null && msg.method) {
|
|
349
|
-
let result = null;
|
|
350
|
-
if (msg.method === "workspace/configuration") {
|
|
351
|
-
result = (msg.params?.items || []).map((item) => item.section ? getSettingsSection(item.section) : {});
|
|
352
|
-
}
|
|
353
|
-
server.stdin.write(encode({ jsonrpc: "2.0", id: msg.id, result }));
|
|
354
|
-
continue;
|
|
355
|
-
}
|
|
356
|
-
// Published diagnostics
|
|
357
|
-
if (msg.method === "textDocument/publishDiagnostics" && msg.params) {
|
|
358
|
-
const uri = msg.params.uri;
|
|
359
|
-
const diags = msg.params.diagnostics || [];
|
|
360
|
-
diagnosticsReceived.set(uri, diags);
|
|
361
|
-
// Resolve URI-specific waiter
|
|
362
|
-
if (diagWaiters.has(uri)) {
|
|
363
|
-
const resolve = diagWaiters.get(uri);
|
|
364
|
-
diagWaiters.delete(uri);
|
|
365
|
-
resolve(diags);
|
|
366
|
-
}
|
|
367
|
-
// Notify the project-aware wait system
|
|
368
|
-
onDiagnosticReceived();
|
|
369
|
-
}
|
|
370
|
-
// Tailwind project initialized
|
|
371
|
-
if (msg.method === "@/tailwindCSS/projectInitialized") {
|
|
372
|
-
onProjectInitialized();
|
|
373
|
-
}
|
|
374
|
-
}
|
|
375
|
-
}
|
|
376
|
-
// ---------------------------------------------------------------------------
|
|
377
|
-
// Server lifecycle
|
|
378
|
-
// ---------------------------------------------------------------------------
|
|
379
|
-
function findLanguageServer(cwd) {
|
|
380
|
-
const local = resolve(cwd, "node_modules/.bin/tailwindcss-language-server");
|
|
381
|
-
return existsSync(local) ? local : "tailwindcss-language-server";
|
|
382
|
-
}
|
|
383
|
-
/** Reject all pending requests and resolve all waiters. Called when the server dies. */
|
|
384
|
-
function drainAll(reason) {
|
|
385
|
-
serverDead = true;
|
|
386
|
-
for (const [id, p] of pending) {
|
|
387
|
-
p.reject(reason);
|
|
388
|
-
pending.delete(id);
|
|
389
|
-
}
|
|
390
|
-
// Resolve project wait (so run() doesn't hang)
|
|
391
|
-
finishWait();
|
|
392
|
-
// Resolve all URI-specific waiters with empty arrays
|
|
393
|
-
for (const [uri, r] of diagWaiters) {
|
|
394
|
-
r([]);
|
|
395
|
-
}
|
|
396
|
-
diagWaiters.clear();
|
|
397
|
-
}
|
|
398
|
-
export function startServer(root) {
|
|
399
|
-
workspaceRoot = root;
|
|
400
|
-
const bin = findLanguageServer(root);
|
|
401
|
-
server = spawn(bin, ["--stdio"], { stdio: ["pipe", "pipe", "pipe"] });
|
|
402
|
-
server.on("error", (err) => {
|
|
403
|
-
if (err.code === "ENOENT") {
|
|
404
|
-
console.error("\n \x1b[38;5;203m\x1b[1mERROR\x1b[0m @tailwindcss/language-server not found.");
|
|
405
|
-
console.error(" Install it: \x1b[1mnpm install -D @tailwindcss/language-server\x1b[0m\n");
|
|
406
|
-
}
|
|
407
|
-
drainAll(new Error(err.code === "ENOENT"
|
|
408
|
-
? "@tailwindcss/language-server not found"
|
|
409
|
-
: `language server error: ${err.message}`));
|
|
410
|
-
});
|
|
411
|
-
server.on("close", (code, signal) => {
|
|
412
|
-
if (!serverDead) {
|
|
413
|
-
drainAll(new Error(signal ? `language server killed by ${signal}` : `language server exited with code ${code}`));
|
|
414
|
-
}
|
|
415
|
-
});
|
|
416
|
-
server.stdout.on("data", (chunk) => {
|
|
417
|
-
chunks.push(chunk);
|
|
418
|
-
chunksLen += chunk.length;
|
|
419
|
-
processMessages();
|
|
420
|
-
});
|
|
421
|
-
server.stderr.on("data", (chunk) => {
|
|
422
|
-
if (DEBUG)
|
|
423
|
-
process.stderr.write(chunk);
|
|
424
|
-
});
|
|
425
|
-
}
|
|
426
|
-
export function send(method, params) {
|
|
427
|
-
if (serverDead)
|
|
428
|
-
return Promise.reject(new Error("language server is not running"));
|
|
429
|
-
const id = ++msgId;
|
|
430
|
-
return new Promise((res, rej) => {
|
|
431
|
-
pending.set(id, { resolve: res, reject: rej });
|
|
432
|
-
try {
|
|
433
|
-
server.stdin.write(encode({ jsonrpc: "2.0", id, method, params }));
|
|
434
|
-
}
|
|
435
|
-
catch {
|
|
436
|
-
pending.delete(id);
|
|
437
|
-
rej(new Error("language server is not running"));
|
|
438
|
-
}
|
|
439
|
-
});
|
|
440
|
-
}
|
|
441
|
-
export function notify(method, params) {
|
|
442
|
-
if (serverDead)
|
|
443
|
-
return;
|
|
444
|
-
try {
|
|
445
|
-
server.stdin.write(encode({ jsonrpc: "2.0", method, params }));
|
|
446
|
-
}
|
|
447
|
-
catch {
|
|
448
|
-
// Server pipe is dead — drainAll will handle cleanup via the close event
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
export async function shutdown() {
|
|
452
|
-
if (serverDead)
|
|
453
|
-
return;
|
|
454
|
-
await Promise.race([
|
|
455
|
-
send("shutdown", {}).catch(() => { }),
|
|
456
|
-
new Promise(r => setTimeout(r, 500)),
|
|
457
|
-
]);
|
|
458
|
-
notify("exit", {});
|
|
459
|
-
serverDead = true;
|
|
460
|
-
try {
|
|
461
|
-
server.stdin.end();
|
|
462
|
-
}
|
|
463
|
-
catch { }
|
|
464
|
-
try {
|
|
465
|
-
server.stdout.destroy();
|
|
466
|
-
}
|
|
467
|
-
catch { }
|
|
468
|
-
try {
|
|
469
|
-
server.stderr.destroy();
|
|
470
|
-
}
|
|
471
|
-
catch { }
|
|
472
|
-
server.kill();
|
|
473
|
-
}
|
|
474
|
-
export function fileUri(absPath) {
|
|
475
|
-
return `file://${absPath}`;
|
|
476
|
-
}
|
|
477
|
-
export function langId(filePath) {
|
|
478
|
-
if (filePath.endsWith(".css"))
|
|
479
|
-
return "css";
|
|
480
|
-
if (filePath.endsWith(".html") || filePath.endsWith(".vue") || filePath.endsWith(".svelte") || filePath.endsWith(".astro"))
|
|
481
|
-
return "html";
|
|
482
|
-
if (filePath.endsWith(".mdx"))
|
|
483
|
-
return "mdx";
|
|
484
|
-
if (filePath.endsWith(".jsx"))
|
|
485
|
-
return "javascriptreact";
|
|
486
|
-
return "typescriptreact";
|
|
487
|
-
}
|
|
1
|
+
import{spawn as q}from"child_process";import{resolve as I}from"path";import{existsSync as $,readFileSync as V}from"fs";const N=process.env.DEBUG==="1";let O="",m=null;function H(){if(m!==null)return m;const e=I(O,".vscode/settings.json");if(!$(e))return m={},m;try{const r=V(e,"utf-8").replace(/\/\/[^\n]*/g,"").replace(/,\s*([\]}])/g,"$1");m=JSON.parse(r)}catch{m={}}return m}function K(e){const r=H(),t=e+".",o={};for(const[l,y]of Object.entries(r)){if(!l.startsWith(t))continue;const c=l.slice(t.length).split(".");let p=o;for(let n=0;n<c.length-1;n++)(!(c[n]in p)||typeof p[c[n]]!="object")&&(p[c[n]]={}),p=p[c[n]];p[c[c.length-1]]=y}return o}let a,d=!1,L=0;const f=[];let T=0;const h=new Map,P=new Map;let A=!1,F=0,D=0,M=0,R=0,B=!1,x=!1,j=0;const J=[];let v=null,i=null,s=null,w=null;const g=new Map;let k={predictedRoots:0,maxProjects:0,initTimeoutMs:5e3,debounceMs:500};function ce(){L=0,d=!1,f.length=0,T=0,h.clear(),P.clear(),A=!1,F=0,D=0,M=0,R=0,B=!1,x=!1,j=0,J.length=0,v=null,i&&(clearTimeout(i),i=null),s&&(clearTimeout(s),s=null),w&&(clearTimeout(w),w=null),g.clear(),m=null}function Q(){i&&(clearTimeout(i),i=null),s&&(clearTimeout(s),s=null),w&&(clearTimeout(w),w=null)}function S(){if(!v)return;const e=v;v=null,Q(),e()}function _(){return D+M>=k.maxProjects}function W(){s&&clearTimeout(s),s=setTimeout(()=>{j>0&&!x?z():S()},k.initTimeoutMs)}function X(){F++;const e=Date.now();A=!0,R>0&&e-R<500?B||(B=!0,M++,J.push("A CSS file failed to initialize (likely an @apply referencing an unknown utility). That project's files will not receive diagnostics. See https://github.com/tailwindlabs/tailwindcss-intellisense/issues/1121"),i&&(clearTimeout(i),i=null)):(B=!1,x=!0,j=0,s&&(clearTimeout(s),s=null),i&&(clearTimeout(i),i=null),W()),R=e,_()&&S()}function z(){D++,_()?S():W()}function Y(){i&&clearTimeout(i),s&&(clearTimeout(s),s=null),i=setTimeout(z,k.debounceMs)}function Z(){v&&(j++,x?x=!1:j>=2&&Y())}function ae(e,r,t=5e3,o=500){return d||r===0?Promise.resolve():(k={predictedRoots:e,maxProjects:r,initTimeoutMs:t,debounceMs:o},new Promise(l=>{v=l,W();const y=t+r*3e3+5e3;w=setTimeout(S,Math.min(y,3e4))}))}function ue(e,r=1e4){return d?Promise.resolve([]):(P.delete(e),new Promise(t=>{const o=setTimeout(()=>{g.has(e)&&(g.delete(e),t([]))},r);g.set(e,l=>{clearTimeout(o),t(l)})}))}function C(e){const r=JSON.stringify(e);return`Content-Length: ${Buffer.byteLength(r)}\r
|
|
2
|
+
\r
|
|
3
|
+
${r}`}function ee(){if(f.length===0)return Buffer.alloc(0);if(f.length===1)return f[0];const e=Buffer.concat(f,T);return f.length=0,f.push(e),e}function E(e){f.length=0,e.length>0?(f.push(e),T=e.length):T=0}function te(){for(;;){const e=ee();if(e.length===0)break;const r=e.toString("ascii",0,Math.min(e.length,256)),t=r.indexOf(`\r
|
|
4
|
+
\r
|
|
5
|
+
`);if(t===-1){E(e);break}const l=r.slice(0,t).match(/Content-Length:\s*(\d+)/i);if(!l){E(e.subarray(t+4));continue}const y=parseInt(l[1],10),c=t+4;if(e.length<c+y){E(e);break}const p=e.subarray(c,c+y).toString("utf-8");E(e.subarray(c+y));let n;try{n=JSON.parse(p)}catch{continue}if(N&&console.error(`<- ${n.method||`response#${n.id}`}`),n.id!=null&&!n.method&&h.has(n.id)){const u=h.get(n.id);h.delete(n.id),n.error?u.reject(n.error):u.resolve(n.result);continue}if(n.id!=null&&n.method){let u=null;n.method==="workspace/configuration"&&(u=(n.params?.items||[]).map(b=>b.section?K(b.section):{})),a.stdin.write(C({jsonrpc:"2.0",id:n.id,result:u}));continue}if(n.method==="textDocument/publishDiagnostics"&&n.params){const u=n.params.uri,b=n.params.diagnostics||[];if(P.set(u,b),g.has(u)){const G=g.get(u);g.delete(u),G(b)}Z()}n.method==="@/tailwindCSS/projectInitialized"&&X()}}function ne(e){const r=I(e,"node_modules/.bin/tailwindcss-language-server");return $(r)?r:"tailwindcss-language-server"}function U(e){d=!0;for(const[r,t]of h)t.reject(e),h.delete(r);S();for(const[r,t]of g)t([]);g.clear()}function fe(e){O=e;const r=ne(e);a=q(r,["--stdio"],{stdio:["pipe","pipe","pipe"]}),a.on("error",t=>{t.code==="ENOENT"&&(console.error(`
|
|
6
|
+
\x1B[38;5;203m\x1B[1mERROR\x1B[0m @tailwindcss/language-server not found.`),console.error(` Install it: \x1B[1mnpm install -D @tailwindcss/language-server\x1B[0m
|
|
7
|
+
`)),U(new Error(t.code==="ENOENT"?"@tailwindcss/language-server not found":`language server error: ${t.message}`))}),a.on("close",(t,o)=>{d||U(new Error(o?`language server killed by ${o}`:`language server exited with code ${t}`))}),a.stdout.on("data",t=>{f.push(t),T+=t.length,te()}),a.stderr.on("data",t=>{N&&process.stderr.write(t)})}function re(e,r){if(d)return Promise.reject(new Error("language server is not running"));const t=++L;return new Promise((o,l)=>{h.set(t,{resolve:o,reject:l});try{a.stdin.write(C({jsonrpc:"2.0",id:t,method:e,params:r}))}catch{h.delete(t),l(new Error("language server is not running"))}})}function ie(e,r){if(!d)try{a.stdin.write(C({jsonrpc:"2.0",method:e,params:r}))}catch{}}async function de(){if(!d){await Promise.race([re("shutdown",{}).catch(()=>{}),new Promise(e=>setTimeout(e,500))]),ie("exit",{}),d=!0;try{a.stdin.end()}catch{}try{a.stdout.destroy()}catch{}try{a.stderr.destroy()}catch{}a.kill()}}function ge(e){return`file://${e}`}function pe(e){return e.endsWith(".css")?"css":e.endsWith(".html")||e.endsWith(".vue")||e.endsWith(".svelte")||e.endsWith(".astro")?"html":e.endsWith(".mdx")?"mdx":e.endsWith(".jsx")?"javascriptreact":"typescriptreact"}export{M as brokenProjects,P as diagnosticsReceived,ge as fileUri,pe as langId,ie as notify,F as projectInitCount,A as projectReady,ce as resetState,re as send,D as settledProjects,de as shutdown,fe as startServer,ae as waitForAllProjects,ue as waitForDiagnostic,J as warnings};
|
package/dist/prescan.js
CHANGED
|
@@ -1,79 +1 @@
|
|
|
1
|
-
|
|
2
|
-
* Pre-scan CSS files to predict how many projects the language server will create.
|
|
3
|
-
*
|
|
4
|
-
* analyzeStylesheet() is adapted from tailwindlabs/tailwindcss-intellisense
|
|
5
|
-
* (packages/tailwindcss-language-server/src/version-guesser.ts, MIT licensed).
|
|
6
|
-
*/
|
|
7
|
-
import { readFileSync } from "fs";
|
|
8
|
-
const HAS_V4_IMPORT = /@import\s*['"]tailwindcss(?:\/[^'"]+)?['"]/;
|
|
9
|
-
const HAS_V4_DIRECTIVE = /@(theme|plugin|utility|custom-variant|variant|reference)\s*[^;{]+[;{]/;
|
|
10
|
-
const HAS_V4_FN = /--(alpha|spacing|theme)\(/;
|
|
11
|
-
const HAS_LEGACY_TAILWIND = /@tailwind\s*(base|preflight|components|variants|screens)+;/;
|
|
12
|
-
const HAS_TAILWIND_UTILITIES = /@tailwind\s*utilities\s*[^;]*;/;
|
|
13
|
-
const HAS_TAILWIND = /@tailwind\s*[^;]+;/;
|
|
14
|
-
const HAS_COMMON_DIRECTIVE = /@(config|apply)\s*[^;{]+[;{]/;
|
|
15
|
-
const HAS_NON_URL_IMPORT = /@import\s*['"](?!([a-z]+:|\/\/))/;
|
|
16
|
-
export function analyzeStylesheet(content) {
|
|
17
|
-
if (HAS_V4_IMPORT.test(content)) {
|
|
18
|
-
return { root: true, versions: ["4"], explicitImport: true };
|
|
19
|
-
}
|
|
20
|
-
if (HAS_V4_DIRECTIVE.test(content)) {
|
|
21
|
-
if (HAS_TAILWIND_UTILITIES.test(content)) {
|
|
22
|
-
return { root: true, versions: ["4"], explicitImport: false };
|
|
23
|
-
}
|
|
24
|
-
return { root: false, versions: ["4"], explicitImport: false };
|
|
25
|
-
}
|
|
26
|
-
if (HAS_V4_FN.test(content)) {
|
|
27
|
-
return { root: false, versions: ["4"], explicitImport: false };
|
|
28
|
-
}
|
|
29
|
-
if (HAS_LEGACY_TAILWIND.test(content)) {
|
|
30
|
-
return { root: false, versions: ["3"], explicitImport: false };
|
|
31
|
-
}
|
|
32
|
-
if (HAS_TAILWIND.test(content)) {
|
|
33
|
-
return { root: true, versions: ["4", "3"], explicitImport: false };
|
|
34
|
-
}
|
|
35
|
-
if (HAS_COMMON_DIRECTIVE.test(content)) {
|
|
36
|
-
return { root: false, versions: ["4", "3"], explicitImport: false };
|
|
37
|
-
}
|
|
38
|
-
if (HAS_NON_URL_IMPORT.test(content)) {
|
|
39
|
-
return { root: true, versions: ["4", "3"], explicitImport: false };
|
|
40
|
-
}
|
|
41
|
-
return { root: false, versions: [], explicitImport: false };
|
|
42
|
-
}
|
|
43
|
-
export function prescanCssFiles(files) {
|
|
44
|
-
let predictedRoots = 0;
|
|
45
|
-
let predictedNonRoots = 0;
|
|
46
|
-
let predictedUnrelated = 0;
|
|
47
|
-
const unrelatedCssFiles = new Set();
|
|
48
|
-
for (const filePath of files) {
|
|
49
|
-
if (!filePath.endsWith(".css"))
|
|
50
|
-
continue;
|
|
51
|
-
let content;
|
|
52
|
-
try {
|
|
53
|
-
content = readFileSync(filePath, "utf-8");
|
|
54
|
-
}
|
|
55
|
-
catch {
|
|
56
|
-
continue;
|
|
57
|
-
}
|
|
58
|
-
const result = analyzeStylesheet(content);
|
|
59
|
-
if (result.versions.length === 0) {
|
|
60
|
-
predictedUnrelated++;
|
|
61
|
-
unrelatedCssFiles.add(filePath);
|
|
62
|
-
}
|
|
63
|
-
else if (result.root) {
|
|
64
|
-
predictedRoots++;
|
|
65
|
-
}
|
|
66
|
-
else {
|
|
67
|
-
predictedNonRoots++;
|
|
68
|
-
}
|
|
69
|
-
}
|
|
70
|
-
const totalCssFiles = predictedRoots + predictedNonRoots + predictedUnrelated;
|
|
71
|
-
return {
|
|
72
|
-
totalCssFiles,
|
|
73
|
-
predictedRoots,
|
|
74
|
-
predictedNonRoots,
|
|
75
|
-
unrelatedCssFiles,
|
|
76
|
-
predictedUnrelated,
|
|
77
|
-
maxProjects: predictedRoots + predictedNonRoots,
|
|
78
|
-
};
|
|
79
|
-
}
|
|
1
|
+
import{readFileSync as a}from"fs";const c=/@import\s*['"]tailwindcss(?:\/[^'"]+)?['"]/,p=/@(theme|plugin|utility|custom-variant|variant|reference)\s*[^;{]+[;{]/,f=/--(alpha|spacing|theme)\(/,u=/@tailwind\s*(base|preflight|components|variants|screens)+;/,d=/@tailwind\s*utilities\s*[^;]*;/,m=/@tailwind\s*[^;]+;/,I=/@(config|apply)\s*[^;{]+[;{]/,x=/@import\s*['"](?!([a-z]+:|\/\/))/;function _(e){return c.test(e)?{root:!0,versions:["4"],explicitImport:!0}:p.test(e)?d.test(e)?{root:!0,versions:["4"],explicitImport:!1}:{root:!1,versions:["4"],explicitImport:!1}:f.test(e)?{root:!1,versions:["4"],explicitImport:!1}:u.test(e)?{root:!1,versions:["3"],explicitImport:!1}:m.test(e)?{root:!0,versions:["4","3"],explicitImport:!1}:I.test(e)?{root:!1,versions:["4","3"],explicitImport:!1}:x.test(e)?{root:!0,versions:["4","3"],explicitImport:!1}:{root:!1,versions:[],explicitImport:!1}}function T(e){let t=0,s=0,r=0;const o=new Set;for(const i of e){if(!i.endsWith(".css"))continue;let n;try{n=a(i,"utf-8")}catch{continue}const l=_(n);l.versions.length===0?(r++,o.add(i)):l.root?t++:s++}return{totalCssFiles:t+s+r,predictedRoots:t,predictedNonRoots:s,unrelatedCssFiles:o,predictedUnrelated:r,maxProjects:t+s}}export{_ as analyzeStylesheet,T as prescanCssFiles};
|