@ape-egg/codie 0.1.3 → 0.1.4
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/highlight.js +137 -43
- package/package.json +1 -1
package/highlight.js
CHANGED
|
@@ -198,53 +198,102 @@ export const highlightJSRaw = (code) => {
|
|
|
198
198
|
return tokens.join('');
|
|
199
199
|
};
|
|
200
200
|
|
|
201
|
-
// CSS highlighter
|
|
201
|
+
// CSS highlighter - walks brace depth so nested rules (@supports, @media, and
|
|
202
|
+
// & selectors) tokenize at every level, not just the innermost block
|
|
202
203
|
export const highlightCSS = (code, options = {}) => {
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
const declarations = (text) => {
|
|
205
|
+
const propRegex = /([\w-]+)(\s*:\s*)([^;]+)(;?)/g;
|
|
206
|
+
let match,
|
|
207
|
+
last = 0,
|
|
208
|
+
result = '';
|
|
209
|
+
|
|
210
|
+
while ((match = propRegex.exec(text)) !== null) {
|
|
211
|
+
if (match.index > last) result += text.slice(last, match.index);
|
|
212
|
+
const [, prop, colon, value, semi] = match;
|
|
213
|
+
result += span(CLASS.CSS_PROPERTY, prop) + span(CLASS.PUNCTUATION, colon);
|
|
214
|
+
const highlighted = value.replace(/@\[([^\]]+)\]/g, (_, expr) => {
|
|
215
|
+
const h = expr.replace(/\b[a-zA-Z_][a-zA-Z0-9_]*\b/g, (v) =>
|
|
216
|
+
span(CLASS.VIBE_VARIABLE, v),
|
|
217
|
+
);
|
|
218
|
+
return `${span(CLASS.VIBE_COLOR, '@[')}${h}${span(CLASS.VIBE_COLOR, ']')}`;
|
|
219
|
+
});
|
|
220
|
+
result += span(CLASS.CSS_VALUE, highlighted);
|
|
221
|
+
if (semi) result += span(CLASS.PUNCTUATION, semi);
|
|
222
|
+
last = propRegex.lastIndex;
|
|
223
|
+
}
|
|
224
|
+
if (last < text.length) result += text.slice(last);
|
|
225
|
+
return result;
|
|
226
|
+
};
|
|
227
|
+
|
|
228
|
+
// Index of the } closing the block opened at `open`, or -1 when unterminated
|
|
229
|
+
const closingBrace = (text, open) => {
|
|
230
|
+
let depth = 1,
|
|
231
|
+
i = open + 1;
|
|
232
|
+
|
|
233
|
+
while (i < text.length && depth) {
|
|
234
|
+
if (text.startsWith('/*', i)) {
|
|
235
|
+
const end = text.indexOf('*/', i + 2);
|
|
236
|
+
i = end === -1 ? text.length : end + 2;
|
|
237
|
+
continue;
|
|
238
|
+
}
|
|
239
|
+
if (text[i] === '{') depth++;
|
|
240
|
+
else if (text[i] === '}') depth--;
|
|
241
|
+
i++;
|
|
242
|
+
}
|
|
207
243
|
|
|
208
|
-
|
|
209
|
-
|
|
244
|
+
return depth ? -1 : i - 1;
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
const block = (text, inRule) => {
|
|
248
|
+
const flush = (chunk) => (inRule ? declarations(chunk) : chunk);
|
|
249
|
+
let result = '',
|
|
250
|
+
i = 0,
|
|
251
|
+
pending = 0;
|
|
252
|
+
|
|
253
|
+
while (i < text.length) {
|
|
254
|
+
if (text.startsWith('/*', i)) {
|
|
255
|
+
const end = text.indexOf('*/', i + 2);
|
|
256
|
+
const stop = end === -1 ? text.length : end + 2;
|
|
257
|
+
result += flush(text.slice(pending, i)) + span(CLASS.COMMENT, text.slice(i, stop));
|
|
258
|
+
i = pending = stop;
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
210
261
|
|
|
211
|
-
|
|
262
|
+
// Statement at-rules (@import, @layer a, b;) close on ; with no block
|
|
263
|
+
if (text[i] === ';' && text.slice(pending, i).trim().startsWith('@')) {
|
|
264
|
+
const statement = text.slice(pending, i);
|
|
265
|
+
result +=
|
|
266
|
+
statement.match(/^(\s*)/)[1] +
|
|
267
|
+
span(CLASS.CSS_SELECTOR, statement.trim()) +
|
|
268
|
+
span(CLASS.PUNCTUATION, ';');
|
|
269
|
+
i = pending = i + 1;
|
|
270
|
+
continue;
|
|
271
|
+
}
|
|
212
272
|
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
propsResult += span(CLASS.CSS_PROPERTY, prop) + span(CLASS.PUNCTUATION, colon);
|
|
229
|
-
const highlighted = value.replace(/@\[([^\]]+)\]/g, (_, expr) => {
|
|
230
|
-
const h = expr.replace(/\b[a-zA-Z_][a-zA-Z0-9_]*\b/g, (v) =>
|
|
231
|
-
span(CLASS.VIBE_VARIABLE, v),
|
|
232
|
-
);
|
|
233
|
-
return `${span(CLASS.VIBE_COLOR, '@[')}${h}${span(CLASS.VIBE_COLOR, ']')}`;
|
|
234
|
-
});
|
|
235
|
-
propsResult += span(CLASS.CSS_VALUE, highlighted);
|
|
236
|
-
if (semi) propsResult += span(CLASS.PUNCTUATION, semi);
|
|
237
|
-
propLast = propRegex.lastIndex;
|
|
273
|
+
if (text[i] === '{') {
|
|
274
|
+
const close = closingBrace(text, i);
|
|
275
|
+
const prelude = text.slice(pending, i);
|
|
276
|
+
const ws = prelude.match(/^(\s*)/)[1];
|
|
277
|
+
const name = prelude.trim();
|
|
278
|
+
|
|
279
|
+
result +=
|
|
280
|
+
ws +
|
|
281
|
+
(name ? span(CLASS.CSS_SELECTOR, name) + ' ' : '') +
|
|
282
|
+
span(CLASS.PUNCTUATION, '{') +
|
|
283
|
+
block(text.slice(i + 1, close === -1 ? text.length : close), true) +
|
|
284
|
+
(close === -1 ? '' : span(CLASS.PUNCTUATION, '}'));
|
|
285
|
+
|
|
286
|
+
i = pending = close === -1 ? text.length : close + 1;
|
|
287
|
+
continue;
|
|
238
288
|
}
|
|
239
|
-
if (propLast < props.length) propsResult += props.slice(propLast);
|
|
240
289
|
|
|
241
|
-
|
|
290
|
+
i++;
|
|
242
291
|
}
|
|
243
|
-
lastIndex = ruleRegex.lastIndex;
|
|
244
|
-
}
|
|
245
292
|
|
|
246
|
-
|
|
247
|
-
|
|
293
|
+
return result + flush(text.slice(pending));
|
|
294
|
+
};
|
|
295
|
+
|
|
296
|
+
return block(code, false);
|
|
248
297
|
};
|
|
249
298
|
|
|
250
299
|
// JSON highlighter (receives HTML-escaped code)
|
|
@@ -310,6 +359,54 @@ export const highlightJSOnly = (html, options = {}) => {
|
|
|
310
359
|
);
|
|
311
360
|
};
|
|
312
361
|
|
|
362
|
+
// Bare CSS rules sitting outside any markup, handed to highlightCSS one whole
|
|
363
|
+
// brace-balanced block at a time. Markup inside a candidate disqualifies it —
|
|
364
|
+
// that is HTML, already highlighted.
|
|
365
|
+
const bareCSS = (text) => {
|
|
366
|
+
let result = '',
|
|
367
|
+
i = 0,
|
|
368
|
+
done = 0;
|
|
369
|
+
|
|
370
|
+
while (i < text.length) {
|
|
371
|
+
if (text[i] !== '{') {
|
|
372
|
+
i++;
|
|
373
|
+
continue;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
let start = i;
|
|
377
|
+
while (start > done && !'<>{}'.includes(text[start - 1])) start--;
|
|
378
|
+
|
|
379
|
+
let depth = 0,
|
|
380
|
+
j = i,
|
|
381
|
+
close = -1;
|
|
382
|
+
|
|
383
|
+
while (j < text.length) {
|
|
384
|
+
const c = text[j];
|
|
385
|
+
if (c === '<' || c === '>') break;
|
|
386
|
+
if (c === '{') depth++;
|
|
387
|
+
else if (c === '}' && !--depth) {
|
|
388
|
+
close = j;
|
|
389
|
+
break;
|
|
390
|
+
}
|
|
391
|
+
j++;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (start === i || close === -1) {
|
|
395
|
+
i++;
|
|
396
|
+
continue;
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
const candidate = text.slice(start, close + 1);
|
|
400
|
+
if (/[\w-]+\s*:\s*/.test(candidate)) {
|
|
401
|
+
result += text.slice(done, start) + highlightCSS(candidate);
|
|
402
|
+
done = close + 1;
|
|
403
|
+
}
|
|
404
|
+
i = close + 1;
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
return result + text.slice(done);
|
|
408
|
+
};
|
|
409
|
+
|
|
313
410
|
// HTML highlighter - main entry point
|
|
314
411
|
export const highlightHTML = (
|
|
315
412
|
html,
|
|
@@ -416,10 +513,7 @@ export const highlightHTML = (
|
|
|
416
513
|
);
|
|
417
514
|
|
|
418
515
|
// CSS rules outside <style> tags (bare CSS blocks in mixed HTML+CSS snippets)
|
|
419
|
-
highlighted = highlighted
|
|
420
|
-
/([^<>{}]+\{[^<>{}]*\})/g,
|
|
421
|
-
(match) => /[\w-]+\s*:\s*/.test(match) ? highlightCSS(match) : match,
|
|
422
|
-
);
|
|
516
|
+
highlighted = bareCSS(highlighted);
|
|
423
517
|
}
|
|
424
518
|
|
|
425
519
|
// Remaining @[...] patterns
|