@blcklab/yuirinx 0.1.1 → 0.1.2
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 +47 -64
- package/dist/cjs/core/index.cjs +36 -7
- package/dist/cjs/index.cjs +37 -8
- package/dist/cjs/languages/c.cjs +1 -1
- package/dist/cjs/languages/cpp.cjs +1 -1
- package/dist/cjs/languages/csharp.cjs +1 -1
- package/dist/cjs/languages/css.cjs +2 -2
- package/dist/cjs/languages/dart.cjs +1 -1
- package/dist/cjs/languages/go.cjs +1 -1
- package/dist/cjs/languages/java.cjs +1 -1
- package/dist/cjs/languages/javascript.cjs +65 -6
- package/dist/cjs/languages/jsx.cjs +65 -6
- package/dist/cjs/languages/kotlin.cjs +1 -1
- package/dist/cjs/languages/markdown.cjs +30 -6
- package/dist/cjs/languages/php.cjs +1 -1
- package/dist/cjs/languages/python.cjs +1 -1
- package/dist/cjs/languages/ruby.cjs +1 -1
- package/dist/cjs/languages/rust.cjs +1 -1
- package/dist/cjs/languages/scss.cjs +2 -2
- package/dist/cjs/languages/sql.cjs +1 -1
- package/dist/cjs/languages/swift.cjs +1 -1
- package/dist/cjs/languages/tsx.cjs +65 -6
- package/dist/cjs/languages/typescript.cjs +65 -6
- package/dist/esm/core/create-highlighter.js +132 -0
- package/dist/esm/core/errors.js +10 -0
- package/dist/esm/core/escape.js +17 -0
- package/dist/esm/core/grammar.js +103 -0
- package/dist/esm/core/index.js +4 -536
- package/dist/esm/core/renderer.js +215 -0
- package/dist/esm/core/tokenizer.js +107 -0
- package/dist/esm/core/types.js +0 -0
- package/dist/esm/index.js +8 -558
- package/dist/esm/languages/bash.js +9 -21
- package/dist/esm/languages/c.js +2 -82
- package/dist/esm/languages/cpp.js +2 -82
- package/dist/esm/languages/csharp.js +2 -82
- package/dist/esm/languages/css.js +2 -64
- package/dist/esm/languages/dart.js +2 -82
- package/dist/esm/languages/dockerfile.js +3 -12
- package/dist/esm/languages/go.js +2 -82
- package/dist/esm/languages/graphql.js +8 -20
- package/dist/esm/languages/html.js +2 -44
- package/dist/esm/languages/ini.js +2 -5
- package/dist/esm/languages/java.js +2 -82
- package/dist/esm/languages/javascript.js +2 -229
- package/dist/esm/languages/json.js +2 -5
- package/dist/esm/languages/jsx.js +2 -229
- package/dist/esm/languages/kotlin.js +2 -82
- package/dist/esm/languages/markdown.js +32 -11
- package/dist/esm/languages/nginx.js +2 -5
- package/dist/esm/languages/php.js +3 -83
- package/dist/esm/languages/plaintext.js +1 -2
- package/dist/esm/languages/python.js +10 -25
- package/dist/esm/languages/ruby.js +9 -24
- package/dist/esm/languages/rust.js +2 -82
- package/dist/esm/languages/scss.js +2 -64
- package/dist/esm/languages/shared/c-family.js +55 -0
- package/dist/esm/languages/shared/css.js +48 -0
- package/dist/esm/languages/shared/ecmascript.js +261 -0
- package/dist/esm/languages/shared/helpers.js +52 -0
- package/dist/esm/languages/shared/markup.js +41 -0
- package/dist/esm/languages/shell.js +1 -115
- package/dist/esm/languages/sql.js +11 -32
- package/dist/esm/languages/svelte.js +2 -44
- package/dist/esm/languages/swift.js +2 -82
- package/dist/esm/languages/toml.js +2 -5
- package/dist/esm/languages/tsx.js +2 -229
- package/dist/esm/languages/typescript.js +2 -229
- package/dist/esm/languages/vue.js +2 -44
- package/dist/esm/languages/xml.js +2 -44
- package/dist/esm/languages/yaml.js +2 -5
- package/dist/esm/singleton.js +24 -0
- package/dist/esm/themes/index.js +3 -188
- package/dist/esm/themes/yuirinx-aurora.js +1 -2
- package/dist/esm/themes/yuirinx-noir.js +1 -2
- package/dist/esm/themes/yuirinx-pearl.js +1 -2
- package/dist/types/core/errors.d.cts +1 -1
- package/dist/types/core/errors.d.ts +1 -1
- package/dist/types/core/index.d.cts +1 -1
- package/dist/types/core/index.d.ts +1 -1
- package/dist/types/core/types.d.cts +5 -1
- package/dist/types/core/types.d.ts +5 -1
- package/dist/types/singleton.d.cts +1 -1
- package/dist/types/singleton.d.ts +1 -1
- package/package.json +4 -4
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
import { escapeHtml } from "./escape.js";
|
|
2
|
+
import { normalizeId } from "./grammar.js";
|
|
3
|
+
const SAFE_COLOR = /^(?:#[\da-f]{3,8}|(?:rgb|hsl)a?\([\d.%+\-,\s]+\)|[a-z]+)$/i;
|
|
4
|
+
function sanitizeColor(value) {
|
|
5
|
+
return value && SAFE_COLOR.test(value.trim()) ? value.trim() : void 0;
|
|
6
|
+
}
|
|
7
|
+
function sanitizeClassPart(value) {
|
|
8
|
+
return value.trim().replace(/[^a-zA-Z0-9_-]+/g, "-").replace(/^-+|-+$/g, "");
|
|
9
|
+
}
|
|
10
|
+
function sanitizeClassList(value) {
|
|
11
|
+
if (!value) return [];
|
|
12
|
+
return value.split(/\s+/).map(sanitizeClassPart).filter(Boolean);
|
|
13
|
+
}
|
|
14
|
+
function safeClassPrefix(prefix) {
|
|
15
|
+
return prefix.replace(/[^a-zA-Z0-9_-]+/g, "") || "tok-";
|
|
16
|
+
}
|
|
17
|
+
function classForToken(type, prefix) {
|
|
18
|
+
const safeType = sanitizeClassPart(type.replace(/\./g, "-")) || "plain";
|
|
19
|
+
return `${safeClassPrefix(prefix)}${safeType}`;
|
|
20
|
+
}
|
|
21
|
+
function classesForToken(type, prefix) {
|
|
22
|
+
const parts = type.split(".").filter(Boolean);
|
|
23
|
+
if (parts.length === 0) return [classForToken("plain", prefix)];
|
|
24
|
+
return parts.map(
|
|
25
|
+
(_, index) => classForToken(parts.slice(0, index + 1).join("."), prefix)
|
|
26
|
+
);
|
|
27
|
+
}
|
|
28
|
+
function classForFallbackLine(index, prefix) {
|
|
29
|
+
return `${safeClassPrefix(prefix)}fallback-line-${index}`;
|
|
30
|
+
}
|
|
31
|
+
function styleToDeclarations(style) {
|
|
32
|
+
if (!style) return "";
|
|
33
|
+
const declarations = [];
|
|
34
|
+
const color = sanitizeColor(style.color);
|
|
35
|
+
const background = sanitizeColor(style.backgroundColor);
|
|
36
|
+
if (color) declarations.push(`color:${color}`);
|
|
37
|
+
if (background) declarations.push(`background-color:${background}`);
|
|
38
|
+
if (style.fontStyle) declarations.push(`font-style:${style.fontStyle}`);
|
|
39
|
+
if (style.fontWeight) {
|
|
40
|
+
const weight = {
|
|
41
|
+
normal: "400",
|
|
42
|
+
medium: "500",
|
|
43
|
+
semibold: "600",
|
|
44
|
+
bold: "700"
|
|
45
|
+
}[style.fontWeight];
|
|
46
|
+
declarations.push(`font-weight:${weight}`);
|
|
47
|
+
}
|
|
48
|
+
if (style.textDecoration)
|
|
49
|
+
declarations.push(`text-decoration:${style.textDecoration}`);
|
|
50
|
+
return declarations.join(";");
|
|
51
|
+
}
|
|
52
|
+
function resolveFallbackPalette(theme) {
|
|
53
|
+
if (!theme) return [];
|
|
54
|
+
const explicit = (theme.fallbackPalette ?? []).map((color) => sanitizeColor(color)).filter((color) => Boolean(color));
|
|
55
|
+
if (explicit.length > 0) return [...new Set(explicit)];
|
|
56
|
+
const inferred = Object.values(theme.tokens).map((style) => sanitizeColor(style.color)).filter((color) => Boolean(color));
|
|
57
|
+
const colors = [...new Set(inferred)].slice(0, 8);
|
|
58
|
+
if (colors.length > 0) return colors;
|
|
59
|
+
const foreground = sanitizeColor(theme.foreground);
|
|
60
|
+
return foreground ? [foreground] : [];
|
|
61
|
+
}
|
|
62
|
+
function hashSource(value) {
|
|
63
|
+
let hash = 2166136261;
|
|
64
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
65
|
+
hash ^= value.charCodeAt(index);
|
|
66
|
+
hash = Math.imul(hash, 16777619);
|
|
67
|
+
}
|
|
68
|
+
return hash >>> 0;
|
|
69
|
+
}
|
|
70
|
+
function renderFallbackLine(value, lineIndex, seed, palette, mode, prefix) {
|
|
71
|
+
const paletteIndex = (seed + lineIndex) % palette.length;
|
|
72
|
+
const escaped = escapeHtml(value);
|
|
73
|
+
if (mode === "classes") {
|
|
74
|
+
const classes = [
|
|
75
|
+
"yuirinx-line",
|
|
76
|
+
classForFallbackLine(paletteIndex, prefix)
|
|
77
|
+
];
|
|
78
|
+
return `<span class="${escapeHtml(classes.join(" "))}">${escaped}</span>`;
|
|
79
|
+
}
|
|
80
|
+
const color = palette[paletteIndex] ?? "";
|
|
81
|
+
const declarations = styleToDeclarations({ color });
|
|
82
|
+
return `<span class="yuirinx-line" style="${escapeHtml(declarations)}">${escaped}</span>`;
|
|
83
|
+
}
|
|
84
|
+
function renderFallbackBody(code, options, theme) {
|
|
85
|
+
if (!code) return "";
|
|
86
|
+
const palette = resolveFallbackPalette(theme);
|
|
87
|
+
if (palette.length === 0) return escapeHtml(code);
|
|
88
|
+
const mode = options.mode ?? "inline";
|
|
89
|
+
const prefix = options.classPrefix ?? "tok-";
|
|
90
|
+
const seed = hashSource(code) % palette.length;
|
|
91
|
+
const newline = /\r\n|\r|\n/g;
|
|
92
|
+
let lineStart = 0;
|
|
93
|
+
let lineIndex = 0;
|
|
94
|
+
let body = "";
|
|
95
|
+
let match;
|
|
96
|
+
while ((match = newline.exec(code)) !== null) {
|
|
97
|
+
body += renderFallbackLine(
|
|
98
|
+
code.slice(lineStart, match.index),
|
|
99
|
+
lineIndex,
|
|
100
|
+
seed,
|
|
101
|
+
palette,
|
|
102
|
+
mode,
|
|
103
|
+
prefix
|
|
104
|
+
);
|
|
105
|
+
body += escapeHtml(match[0]);
|
|
106
|
+
lineStart = newline.lastIndex;
|
|
107
|
+
lineIndex += 1;
|
|
108
|
+
}
|
|
109
|
+
if (lineStart < code.length) {
|
|
110
|
+
body += renderFallbackLine(
|
|
111
|
+
code.slice(lineStart),
|
|
112
|
+
lineIndex,
|
|
113
|
+
seed,
|
|
114
|
+
palette,
|
|
115
|
+
mode,
|
|
116
|
+
prefix
|
|
117
|
+
);
|
|
118
|
+
}
|
|
119
|
+
return body;
|
|
120
|
+
}
|
|
121
|
+
function wrapBody(body, options, theme, fallbackLines) {
|
|
122
|
+
if (options.wrap === false) return body;
|
|
123
|
+
const mode = options.mode ?? "inline";
|
|
124
|
+
const normalizedThemeId = theme ? sanitizeClassPart(normalizeId(theme.id)) : "";
|
|
125
|
+
const themeId = normalizedThemeId.startsWith("yuirinx-") ? normalizedThemeId.slice("yuirinx-".length) : normalizedThemeId;
|
|
126
|
+
const preClasses = [
|
|
127
|
+
"yuirinx",
|
|
128
|
+
themeId ? `yuirinx-theme-${themeId}` : "",
|
|
129
|
+
fallbackLines ? "yuirinx-fallback-lines" : "",
|
|
130
|
+
...sanitizeClassList(options.preClass)
|
|
131
|
+
].filter(Boolean);
|
|
132
|
+
const language = sanitizeClassPart(normalizeId(options.language ?? "plaintext")) || "plaintext";
|
|
133
|
+
const codeClasses = [
|
|
134
|
+
`language-${language}`,
|
|
135
|
+
...sanitizeClassList(options.codeClass)
|
|
136
|
+
];
|
|
137
|
+
const containerStyle = mode === "inline" && theme ? styleToDeclarations({
|
|
138
|
+
color: theme.foreground,
|
|
139
|
+
backgroundColor: theme.background
|
|
140
|
+
}) : "";
|
|
141
|
+
return `<pre class="${escapeHtml(preClasses.join(" "))}"${containerStyle ? ` style="${escapeHtml(containerStyle)}"` : ""}><code class="${escapeHtml(codeClasses.join(" "))}">${body}</code></pre>`;
|
|
142
|
+
}
|
|
143
|
+
function resolveTokenStyle(theme, type) {
|
|
144
|
+
if (!theme) return void 0;
|
|
145
|
+
let current = type;
|
|
146
|
+
while (current) {
|
|
147
|
+
const style = theme.tokens[current];
|
|
148
|
+
if (style) return style;
|
|
149
|
+
const separator = current.lastIndexOf(".");
|
|
150
|
+
if (separator < 0) break;
|
|
151
|
+
current = current.slice(0, separator);
|
|
152
|
+
}
|
|
153
|
+
return void 0;
|
|
154
|
+
}
|
|
155
|
+
function renderTokens(tokens, options, theme) {
|
|
156
|
+
const mode = options.mode ?? "inline";
|
|
157
|
+
const prefix = options.classPrefix ?? "tok-";
|
|
158
|
+
const body = tokens.map((token) => {
|
|
159
|
+
const escaped = escapeHtml(token.value);
|
|
160
|
+
if (token.type === "plain") return escaped;
|
|
161
|
+
if (mode === "classes") {
|
|
162
|
+
const classes = classesForToken(token.type, prefix);
|
|
163
|
+
return `<span class="${escapeHtml(classes.join(" "))}">${escaped}</span>`;
|
|
164
|
+
}
|
|
165
|
+
const declarations = styleToDeclarations(
|
|
166
|
+
resolveTokenStyle(theme, token.type)
|
|
167
|
+
);
|
|
168
|
+
return declarations ? `<span style="${escapeHtml(declarations)}">${escaped}</span>` : escaped;
|
|
169
|
+
}).join("");
|
|
170
|
+
return wrapBody(body, options, theme, false);
|
|
171
|
+
}
|
|
172
|
+
function renderColorLines(code, options, theme) {
|
|
173
|
+
const colored = code.length > 0 && resolveFallbackPalette(theme).length > 0;
|
|
174
|
+
const body = renderFallbackBody(code, options, theme);
|
|
175
|
+
return wrapBody(body, options, theme, colored);
|
|
176
|
+
}
|
|
177
|
+
function themeToCss(theme, options = {}) {
|
|
178
|
+
const prefix = options.classPrefix ?? "tok-";
|
|
179
|
+
const normalizedThemeId = sanitizeClassPart(normalizeId(theme.id));
|
|
180
|
+
const themeId = normalizedThemeId.startsWith("yuirinx-") ? normalizedThemeId.slice("yuirinx-".length) : normalizedThemeId;
|
|
181
|
+
const rawSelector = options.selector ?? `.yuirinx-theme-${themeId || "default"}`;
|
|
182
|
+
const selector = rawSelector.replace(/[{}<>]/g, "").trim() || ".yuirinx";
|
|
183
|
+
const lines = [];
|
|
184
|
+
if (options.includeContainer !== false) {
|
|
185
|
+
const foreground = sanitizeColor(theme.foreground);
|
|
186
|
+
const background = sanitizeColor(theme.background);
|
|
187
|
+
const declarations = [
|
|
188
|
+
foreground ? `color:${foreground}` : "",
|
|
189
|
+
background ? `background-color:${background}` : ""
|
|
190
|
+
].filter(Boolean);
|
|
191
|
+
lines.push(`${selector}{${declarations.join(";")}}`);
|
|
192
|
+
}
|
|
193
|
+
const tokenStyles = Object.entries(theme.tokens).sort(
|
|
194
|
+
([left], [right]) => left.split(".").length - right.split(".").length
|
|
195
|
+
);
|
|
196
|
+
for (const [type, style] of tokenStyles) {
|
|
197
|
+
const declarations = styleToDeclarations(style);
|
|
198
|
+
if (declarations)
|
|
199
|
+
lines.push(
|
|
200
|
+
`${selector} .${classForToken(type, prefix)}{${declarations}}`
|
|
201
|
+
);
|
|
202
|
+
}
|
|
203
|
+
for (const [index, color] of resolveFallbackPalette(theme).entries()) {
|
|
204
|
+
lines.push(
|
|
205
|
+
`${selector} .${classForFallbackLine(index, prefix)}{color:${color}}`
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
return lines.join("\n");
|
|
209
|
+
}
|
|
210
|
+
export {
|
|
211
|
+
renderColorLines,
|
|
212
|
+
renderTokens,
|
|
213
|
+
resolveTokenStyle,
|
|
214
|
+
themeToCss
|
|
215
|
+
};
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { YuirinxError } from "./errors.js";
|
|
2
|
+
function resolveValue(resolver, match, context) {
|
|
3
|
+
return typeof resolver === "function" ? resolver(match, context) : resolver;
|
|
4
|
+
}
|
|
5
|
+
function appendToken(tokens, type, value, start) {
|
|
6
|
+
if (!value) return;
|
|
7
|
+
const previous = tokens[tokens.length - 1];
|
|
8
|
+
if (previous && previous.type === type && previous.end === start) {
|
|
9
|
+
tokens[tokens.length - 1] = {
|
|
10
|
+
type,
|
|
11
|
+
value: previous.value + value,
|
|
12
|
+
start: previous.start,
|
|
13
|
+
end: start + value.length
|
|
14
|
+
};
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
tokens.push({ type, value, start, end: start + value.length });
|
|
18
|
+
}
|
|
19
|
+
function publicStack(grammarId, stack) {
|
|
20
|
+
return stack.map((name) => ({ name, language: grammarId }));
|
|
21
|
+
}
|
|
22
|
+
function applyTransition(rule, match, context, stack, maxStateDepth) {
|
|
23
|
+
const popCount = rule.pop === true ? 1 : typeof rule.pop === "number" ? rule.pop : 0;
|
|
24
|
+
if (popCount > 0) {
|
|
25
|
+
const removable = Math.min(popCount, Math.max(0, stack.length - 1));
|
|
26
|
+
stack.splice(stack.length - removable, removable);
|
|
27
|
+
}
|
|
28
|
+
const next = resolveValue(
|
|
29
|
+
rule.next,
|
|
30
|
+
match,
|
|
31
|
+
context
|
|
32
|
+
);
|
|
33
|
+
if (next) stack[stack.length - 1] = next;
|
|
34
|
+
const push = resolveValue(
|
|
35
|
+
rule.push,
|
|
36
|
+
match,
|
|
37
|
+
context
|
|
38
|
+
);
|
|
39
|
+
if (push) {
|
|
40
|
+
if (stack.length >= maxStateDepth) {
|
|
41
|
+
throw new YuirinxError(
|
|
42
|
+
"YUIRINX_STATE_DEPTH_EXCEEDED",
|
|
43
|
+
`Tokenizer state depth exceeded the configured limit of ${maxStateDepth}.`
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
stack.push(push);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
function tokenizeCompiled(code, grammar, maxStateDepth) {
|
|
50
|
+
if (!code) return [];
|
|
51
|
+
const tokens = [];
|
|
52
|
+
const stack = [grammar.initialState];
|
|
53
|
+
let offset = 0;
|
|
54
|
+
while (offset < code.length) {
|
|
55
|
+
const stateName = stack[stack.length - 1] ?? grammar.initialState;
|
|
56
|
+
const rules = grammar.states[stateName];
|
|
57
|
+
if (!rules) {
|
|
58
|
+
throw new YuirinxError(
|
|
59
|
+
"YUIRINX_UNKNOWN_STATE",
|
|
60
|
+
`Grammar "${grammar.id}" entered missing state "${stateName}".`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
let matched = false;
|
|
64
|
+
for (const rule of rules) {
|
|
65
|
+
rule.pattern.lastIndex = offset;
|
|
66
|
+
const match = rule.pattern.exec(code);
|
|
67
|
+
if (!match) continue;
|
|
68
|
+
if (match[0].length === 0) {
|
|
69
|
+
throw new YuirinxError(
|
|
70
|
+
"YUIRINX_EMPTY_PATTERN",
|
|
71
|
+
`Grammar "${grammar.id}" produced an empty match in state "${stateName}" from /${rule.source}/.`
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
const context = {
|
|
75
|
+
source: code,
|
|
76
|
+
language: grammar.id,
|
|
77
|
+
state: stateName,
|
|
78
|
+
stack: publicStack(grammar.id, stack),
|
|
79
|
+
offset
|
|
80
|
+
};
|
|
81
|
+
if (rule.when && !rule.when(match, context)) continue;
|
|
82
|
+
const tokenType = resolveValue(
|
|
83
|
+
rule.type,
|
|
84
|
+
match,
|
|
85
|
+
context
|
|
86
|
+
) ?? grammar.fallbackTypes[stateName] ?? "plain";
|
|
87
|
+
appendToken(tokens, tokenType, match[0], offset);
|
|
88
|
+
applyTransition(rule, match, context, stack, maxStateDepth);
|
|
89
|
+
offset += match[0].length;
|
|
90
|
+
matched = true;
|
|
91
|
+
break;
|
|
92
|
+
}
|
|
93
|
+
if (!matched) {
|
|
94
|
+
appendToken(
|
|
95
|
+
tokens,
|
|
96
|
+
grammar.fallbackTypes[stateName] ?? "plain",
|
|
97
|
+
code[offset] ?? "",
|
|
98
|
+
offset
|
|
99
|
+
);
|
|
100
|
+
offset += 1;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return tokens;
|
|
104
|
+
}
|
|
105
|
+
export {
|
|
106
|
+
tokenizeCompiled
|
|
107
|
+
};
|
|
File without changes
|