juxscript 1.1.57 → 1.1.59
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/lib/utils/codeparser.d.ts.map +1 -1
- package/lib/utils/codeparser.js +226 -8
- package/lib/utils/codeparser.ts +230 -8
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"codeparser.d.ts","sourceRoot":"","sources":["codeparser.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"codeparser.d.ts","sourceRoot":"","sources":["codeparser.ts"],"names":[],"mappings":"AA2DA,MAAM,WAAW,UAAU;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;CACf;AAED;;GAEG;AACH,iBAAS,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAOxC;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,GAAE,MAAqB,GAAG,UAAU,EAAE,CAQrF;AAwOD;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,UAAU,EAAE,UAAU,GAAG,MAAM,CAEnE;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,CAkG9C;;;;;;;AAED,wBAKE"}
|
package/lib/utils/codeparser.js
CHANGED
|
@@ -12,6 +12,43 @@ const KEYWORDS = new Set([
|
|
|
12
12
|
'!=', '!==', '+', '-', '*', '/', '%', '++', '--', '&&', '||', '!', '<', '>',
|
|
13
13
|
'<=', '>=', '=>', '...', '.', ',', '(', ')', '{', '}', '[', ']', ':', '?'
|
|
14
14
|
]);
|
|
15
|
+
/**
|
|
16
|
+
* Categorized token sets for semantic highlighting
|
|
17
|
+
*/
|
|
18
|
+
// Control flow keywords (loops, conditionals, etc.)
|
|
19
|
+
const CONTROL_FLOW = new Set([
|
|
20
|
+
'if', 'else', 'switch', 'case', 'default',
|
|
21
|
+
'for', 'while', 'do', 'break', 'continue',
|
|
22
|
+
'return', 'throw', 'try', 'catch', 'finally'
|
|
23
|
+
]);
|
|
24
|
+
// Declaration keywords
|
|
25
|
+
const DECLARATIONS = new Set([
|
|
26
|
+
'const', 'let', 'var', 'function', 'class',
|
|
27
|
+
'import', 'export', 'from', 'as',
|
|
28
|
+
'interface', 'type', 'enum', 'namespace',
|
|
29
|
+
'static', 'get', 'set', 'extends'
|
|
30
|
+
]);
|
|
31
|
+
// Operator keywords
|
|
32
|
+
const OPERATORS = new Set([
|
|
33
|
+
'new', 'delete', 'typeof', 'instanceof',
|
|
34
|
+
'void', 'await', 'yield', 'in', 'of'
|
|
35
|
+
]);
|
|
36
|
+
// Special keywords
|
|
37
|
+
const SPECIAL = new Set([
|
|
38
|
+
'this', 'super', 'async', 'debugger', 'with'
|
|
39
|
+
]);
|
|
40
|
+
// JUX-specific keywords
|
|
41
|
+
const JUX_KEYWORDS = new Set([
|
|
42
|
+
'jux', 'state', 'registry', 'render', 'bind', 'sync'
|
|
43
|
+
]);
|
|
44
|
+
// Combine all keywords for quick lookup
|
|
45
|
+
const ALL_KEYWORDS = new Set([
|
|
46
|
+
...CONTROL_FLOW,
|
|
47
|
+
...DECLARATIONS,
|
|
48
|
+
...OPERATORS,
|
|
49
|
+
...SPECIAL,
|
|
50
|
+
...JUX_KEYWORDS
|
|
51
|
+
]);
|
|
15
52
|
/**
|
|
16
53
|
* Escape HTML entities
|
|
17
54
|
*/
|
|
@@ -35,7 +72,85 @@ export function parseCode(code, language = 'javascript') {
|
|
|
35
72
|
}));
|
|
36
73
|
}
|
|
37
74
|
/**
|
|
38
|
-
*
|
|
75
|
+
* Get the appropriate CSS class for a keyword
|
|
76
|
+
*/
|
|
77
|
+
function getKeywordClass(word) {
|
|
78
|
+
if (CONTROL_FLOW.has(word))
|
|
79
|
+
return 'token-control';
|
|
80
|
+
if (DECLARATIONS.has(word))
|
|
81
|
+
return 'token-declaration';
|
|
82
|
+
if (OPERATORS.has(word))
|
|
83
|
+
return 'token-operator-keyword';
|
|
84
|
+
if (SPECIAL.has(word))
|
|
85
|
+
return 'token-special';
|
|
86
|
+
if (JUX_KEYWORDS.has(word))
|
|
87
|
+
return 'token-jux';
|
|
88
|
+
return 'token-keyword'; // fallback
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Check if character is a structural punctuation (braces, brackets, parens)
|
|
92
|
+
*/
|
|
93
|
+
function isStructural(char) {
|
|
94
|
+
return '(){}[]'.includes(char);
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if character is a delimiter (semicolon, comma, colon)
|
|
98
|
+
*/
|
|
99
|
+
function isDelimiter(char) {
|
|
100
|
+
return ';,:'.includes(char);
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Check if character is an operator
|
|
104
|
+
*/
|
|
105
|
+
function isOperator(char) {
|
|
106
|
+
return '+-*/%=<>!&|^~?'.includes(char);
|
|
107
|
+
}
|
|
108
|
+
/**
|
|
109
|
+
* Check if character starts a multi-char operator
|
|
110
|
+
*/
|
|
111
|
+
function consumeOperator(line, i) {
|
|
112
|
+
const char = line[i];
|
|
113
|
+
const next = line[i + 1];
|
|
114
|
+
const next2 = line[i + 2];
|
|
115
|
+
// Three-char operators
|
|
116
|
+
if (char === '=' && next === '=' && next2 === '=')
|
|
117
|
+
return '===';
|
|
118
|
+
if (char === '!' && next === '=' && next2 === '=')
|
|
119
|
+
return '!==';
|
|
120
|
+
if (char === '>' && next === '>' && next2 === '>')
|
|
121
|
+
return '>>>';
|
|
122
|
+
// Two-char operators
|
|
123
|
+
if (char === '=' && next === '=')
|
|
124
|
+
return '==';
|
|
125
|
+
if (char === '!' && next === '=')
|
|
126
|
+
return '!=';
|
|
127
|
+
if (char === '<' && next === '=')
|
|
128
|
+
return '<=';
|
|
129
|
+
if (char === '>' && next === '=')
|
|
130
|
+
return '>=';
|
|
131
|
+
if (char === '&' && next === '&')
|
|
132
|
+
return '&&';
|
|
133
|
+
if (char === '|' && next === '|')
|
|
134
|
+
return '||';
|
|
135
|
+
if (char === '+' && next === '+')
|
|
136
|
+
return '++';
|
|
137
|
+
if (char === '-' && next === '-')
|
|
138
|
+
return '--';
|
|
139
|
+
if (char === '=' && next === '>')
|
|
140
|
+
return '=>';
|
|
141
|
+
if (char === '>' && next === '>')
|
|
142
|
+
return '>>';
|
|
143
|
+
if (char === '<' && next === '<')
|
|
144
|
+
return '<<';
|
|
145
|
+
if (char === '*' && next === '*')
|
|
146
|
+
return '**';
|
|
147
|
+
if (char === '.' && next === '.' && next2 === '.')
|
|
148
|
+
return '...';
|
|
149
|
+
// Single-char operator
|
|
150
|
+
return char;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Tokenize a single line character-by-character with semantic categories
|
|
39
154
|
*/
|
|
40
155
|
function tokenizeLine(line) {
|
|
41
156
|
if (!line.trim())
|
|
@@ -43,6 +158,10 @@ function tokenizeLine(line) {
|
|
|
43
158
|
let result = '';
|
|
44
159
|
let i = 0;
|
|
45
160
|
const len = line.length;
|
|
161
|
+
// Check for comment at start
|
|
162
|
+
if (line.trim().startsWith('//')) {
|
|
163
|
+
return `<span class="token-comment">${escapeHtml(line)}</span>`;
|
|
164
|
+
}
|
|
46
165
|
while (i < len) {
|
|
47
166
|
const char = line[i];
|
|
48
167
|
// Whitespace - preserve
|
|
@@ -51,12 +170,59 @@ function tokenizeLine(line) {
|
|
|
51
170
|
i++;
|
|
52
171
|
continue;
|
|
53
172
|
}
|
|
173
|
+
// Comment (// or /* */)
|
|
174
|
+
if (char === '/' && i + 1 < len) {
|
|
175
|
+
if (line[i + 1] === '/') {
|
|
176
|
+
// Line comment - consume rest of line
|
|
177
|
+
const comment = line.slice(i);
|
|
178
|
+
result += `<span class="token-comment">${escapeHtml(comment)}</span>`;
|
|
179
|
+
break;
|
|
180
|
+
}
|
|
181
|
+
else if (line[i + 1] === '*') {
|
|
182
|
+
// Block comment start
|
|
183
|
+
const commentEnd = line.indexOf('*/', i + 2);
|
|
184
|
+
if (commentEnd !== -1) {
|
|
185
|
+
const comment = line.slice(i, commentEnd + 2);
|
|
186
|
+
result += `<span class="token-comment">${escapeHtml(comment)}</span>`;
|
|
187
|
+
i = commentEnd + 2;
|
|
188
|
+
continue;
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
// String literals (single, double, backtick)
|
|
193
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
194
|
+
let j = i + 1;
|
|
195
|
+
let escaped = false;
|
|
196
|
+
// Find matching quote
|
|
197
|
+
while (j < len) {
|
|
198
|
+
if (escaped) {
|
|
199
|
+
escaped = false;
|
|
200
|
+
j++;
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
if (line[j] === '\\') {
|
|
204
|
+
escaped = true;
|
|
205
|
+
j++;
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
if (line[j] === char) {
|
|
209
|
+
j++;
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
j++;
|
|
213
|
+
}
|
|
214
|
+
const str = line.slice(i, j);
|
|
215
|
+
result += `<span class="token-string">${escapeHtml(str)}</span>`;
|
|
216
|
+
i = j;
|
|
217
|
+
continue;
|
|
218
|
+
}
|
|
54
219
|
// Word (identifier or keyword)
|
|
55
220
|
if (/[a-zA-Z_$]/.test(char)) {
|
|
56
221
|
const word = consumeWord(line, i);
|
|
57
222
|
const escaped = escapeHtml(word);
|
|
58
|
-
if (
|
|
59
|
-
|
|
223
|
+
if (ALL_KEYWORDS.has(word)) {
|
|
224
|
+
const tokenClass = getKeywordClass(word);
|
|
225
|
+
result += `<span class="${tokenClass}">${escaped}</span>`;
|
|
60
226
|
}
|
|
61
227
|
else {
|
|
62
228
|
result += escaped;
|
|
@@ -71,6 +237,25 @@ function tokenizeLine(line) {
|
|
|
71
237
|
i += num.length;
|
|
72
238
|
continue;
|
|
73
239
|
}
|
|
240
|
+
// Operators (multi-char)
|
|
241
|
+
if (isOperator(char)) {
|
|
242
|
+
const op = consumeOperator(line, i);
|
|
243
|
+
result += `<span class="token-operator">${escapeHtml(op)}</span>`;
|
|
244
|
+
i += op.length;
|
|
245
|
+
continue;
|
|
246
|
+
}
|
|
247
|
+
// Structural punctuation (braces, brackets, parens)
|
|
248
|
+
if (isStructural(char)) {
|
|
249
|
+
result += `<span class="token-structural">${escapeHtml(char)}</span>`;
|
|
250
|
+
i++;
|
|
251
|
+
continue;
|
|
252
|
+
}
|
|
253
|
+
// Delimiters (semicolon, comma, colon)
|
|
254
|
+
if (isDelimiter(char)) {
|
|
255
|
+
result += `<span class="token-delimiter">${escapeHtml(char)}</span>`;
|
|
256
|
+
i++;
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
74
259
|
// Everything else - just escape and append
|
|
75
260
|
result += escapeHtml(char);
|
|
76
261
|
i++;
|
|
@@ -119,7 +304,7 @@ export function renderLineWithTokens(parsedLine) {
|
|
|
119
304
|
*/
|
|
120
305
|
export function getSyntaxHighlightCSS() {
|
|
121
306
|
return `
|
|
122
|
-
/*
|
|
307
|
+
/* Semantic Code Highlighting */
|
|
123
308
|
.jux-code {
|
|
124
309
|
font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;
|
|
125
310
|
font-size: 14px;
|
|
@@ -173,13 +358,46 @@ export function getSyntaxHighlightCSS() {
|
|
|
173
358
|
display: none;
|
|
174
359
|
}
|
|
175
360
|
|
|
176
|
-
/* Token colors */
|
|
177
|
-
.token-
|
|
178
|
-
color: #c678dd;
|
|
361
|
+
/* Token colors - Semantic categories */
|
|
362
|
+
.token-control {
|
|
363
|
+
color: #c678dd; /* Purple - control flow (if, for, return) */
|
|
179
364
|
font-weight: 600;
|
|
180
365
|
}
|
|
366
|
+
.token-declaration {
|
|
367
|
+
color: #61afef; /* Blue - declarations (const, let, function) */
|
|
368
|
+
font-weight: 600;
|
|
369
|
+
}
|
|
370
|
+
.token-operator-keyword {
|
|
371
|
+
color: #e06c75; /* Red - operator keywords (typeof, instanceof) */
|
|
372
|
+
font-weight: 600;
|
|
373
|
+
}
|
|
374
|
+
.token-special {
|
|
375
|
+
color: #e5c07b; /* Yellow - special keywords (this, super, async) */
|
|
376
|
+
font-weight: 600;
|
|
377
|
+
}
|
|
378
|
+
.token-jux {
|
|
379
|
+
color: #56b6c2; /* Cyan - JUX-specific (jux, state, render) */
|
|
380
|
+
font-weight: 700;
|
|
381
|
+
}
|
|
181
382
|
.token-number {
|
|
182
|
-
color: #d19a66;
|
|
383
|
+
color: #d19a66; /* Orange - numbers */
|
|
384
|
+
}
|
|
385
|
+
.token-string {
|
|
386
|
+
color: #98c379; /* Green - strings */
|
|
387
|
+
}
|
|
388
|
+
.token-comment {
|
|
389
|
+
color: #5c6370; /* Gray - comments */
|
|
390
|
+
font-style: italic;
|
|
391
|
+
}
|
|
392
|
+
.token-structural {
|
|
393
|
+
color: #abb2bf; /* Light gray - braces, brackets, parens */
|
|
394
|
+
font-weight: 600;
|
|
395
|
+
}
|
|
396
|
+
.token-delimiter {
|
|
397
|
+
color: #777; /* Dark gray - semicolons, commas, colons */
|
|
398
|
+
}
|
|
399
|
+
.token-operator {
|
|
400
|
+
color: #56b6c2; /* Cyan - operators (+, -, *, etc.) */
|
|
183
401
|
}
|
|
184
402
|
`;
|
|
185
403
|
}
|
package/lib/utils/codeparser.ts
CHANGED
|
@@ -13,6 +13,50 @@ const KEYWORDS = new Set([
|
|
|
13
13
|
'<=', '>=', '=>', '...', '.', ',', '(', ')', '{', '}', '[', ']', ':', '?'
|
|
14
14
|
]);
|
|
15
15
|
|
|
16
|
+
/**
|
|
17
|
+
* Categorized token sets for semantic highlighting
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// Control flow keywords (loops, conditionals, etc.)
|
|
21
|
+
const CONTROL_FLOW = new Set([
|
|
22
|
+
'if', 'else', 'switch', 'case', 'default',
|
|
23
|
+
'for', 'while', 'do', 'break', 'continue',
|
|
24
|
+
'return', 'throw', 'try', 'catch', 'finally'
|
|
25
|
+
]);
|
|
26
|
+
|
|
27
|
+
// Declaration keywords
|
|
28
|
+
const DECLARATIONS = new Set([
|
|
29
|
+
'const', 'let', 'var', 'function', 'class',
|
|
30
|
+
'import', 'export', 'from', 'as',
|
|
31
|
+
'interface', 'type', 'enum', 'namespace',
|
|
32
|
+
'static', 'get', 'set', 'extends'
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
// Operator keywords
|
|
36
|
+
const OPERATORS = new Set([
|
|
37
|
+
'new', 'delete', 'typeof', 'instanceof',
|
|
38
|
+
'void', 'await', 'yield', 'in', 'of'
|
|
39
|
+
]);
|
|
40
|
+
|
|
41
|
+
// Special keywords
|
|
42
|
+
const SPECIAL = new Set([
|
|
43
|
+
'this', 'super', 'async', 'debugger', 'with'
|
|
44
|
+
]);
|
|
45
|
+
|
|
46
|
+
// JUX-specific keywords
|
|
47
|
+
const JUX_KEYWORDS = new Set([
|
|
48
|
+
'jux', 'state', 'registry', 'render', 'bind', 'sync'
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
// Combine all keywords for quick lookup
|
|
52
|
+
const ALL_KEYWORDS = new Set([
|
|
53
|
+
...CONTROL_FLOW,
|
|
54
|
+
...DECLARATIONS,
|
|
55
|
+
...OPERATORS,
|
|
56
|
+
...SPECIAL,
|
|
57
|
+
...JUX_KEYWORDS
|
|
58
|
+
]);
|
|
59
|
+
|
|
16
60
|
export interface ParsedLine {
|
|
17
61
|
lineNumber: number;
|
|
18
62
|
html: string;
|
|
@@ -45,7 +89,72 @@ export function parseCode(code: string, language: string = 'javascript'): Parsed
|
|
|
45
89
|
}
|
|
46
90
|
|
|
47
91
|
/**
|
|
48
|
-
*
|
|
92
|
+
* Get the appropriate CSS class for a keyword
|
|
93
|
+
*/
|
|
94
|
+
function getKeywordClass(word: string): string {
|
|
95
|
+
if (CONTROL_FLOW.has(word)) return 'token-control';
|
|
96
|
+
if (DECLARATIONS.has(word)) return 'token-declaration';
|
|
97
|
+
if (OPERATORS.has(word)) return 'token-operator-keyword';
|
|
98
|
+
if (SPECIAL.has(word)) return 'token-special';
|
|
99
|
+
if (JUX_KEYWORDS.has(word)) return 'token-jux';
|
|
100
|
+
return 'token-keyword'; // fallback
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Check if character is a structural punctuation (braces, brackets, parens)
|
|
105
|
+
*/
|
|
106
|
+
function isStructural(char: string): boolean {
|
|
107
|
+
return '(){}[]'.includes(char);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Check if character is a delimiter (semicolon, comma, colon)
|
|
112
|
+
*/
|
|
113
|
+
function isDelimiter(char: string): boolean {
|
|
114
|
+
return ';,:'.includes(char);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Check if character is an operator
|
|
119
|
+
*/
|
|
120
|
+
function isOperator(char: string): boolean {
|
|
121
|
+
return '+-*/%=<>!&|^~?'.includes(char);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Check if character starts a multi-char operator
|
|
126
|
+
*/
|
|
127
|
+
function consumeOperator(line: string, i: number): string {
|
|
128
|
+
const char = line[i];
|
|
129
|
+
const next = line[i + 1];
|
|
130
|
+
const next2 = line[i + 2];
|
|
131
|
+
|
|
132
|
+
// Three-char operators
|
|
133
|
+
if (char === '=' && next === '=' && next2 === '=') return '===';
|
|
134
|
+
if (char === '!' && next === '=' && next2 === '=') return '!==';
|
|
135
|
+
if (char === '>' && next === '>' && next2 === '>') return '>>>';
|
|
136
|
+
|
|
137
|
+
// Two-char operators
|
|
138
|
+
if (char === '=' && next === '=') return '==';
|
|
139
|
+
if (char === '!' && next === '=') return '!=';
|
|
140
|
+
if (char === '<' && next === '=') return '<=';
|
|
141
|
+
if (char === '>' && next === '=') return '>=';
|
|
142
|
+
if (char === '&' && next === '&') return '&&';
|
|
143
|
+
if (char === '|' && next === '|') return '||';
|
|
144
|
+
if (char === '+' && next === '+') return '++';
|
|
145
|
+
if (char === '-' && next === '-') return '--';
|
|
146
|
+
if (char === '=' && next === '>') return '=>';
|
|
147
|
+
if (char === '>' && next === '>') return '>>';
|
|
148
|
+
if (char === '<' && next === '<') return '<<';
|
|
149
|
+
if (char === '*' && next === '*') return '**';
|
|
150
|
+
if (char === '.' && next === '.' && next2 === '.') return '...';
|
|
151
|
+
|
|
152
|
+
// Single-char operator
|
|
153
|
+
return char;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Tokenize a single line character-by-character with semantic categories
|
|
49
158
|
*/
|
|
50
159
|
function tokenizeLine(line: string): string {
|
|
51
160
|
if (!line.trim()) return ' ';
|
|
@@ -54,6 +163,11 @@ function tokenizeLine(line: string): string {
|
|
|
54
163
|
let i = 0;
|
|
55
164
|
const len = line.length;
|
|
56
165
|
|
|
166
|
+
// Check for comment at start
|
|
167
|
+
if (line.trim().startsWith('//')) {
|
|
168
|
+
return `<span class="token-comment">${escapeHtml(line)}</span>`;
|
|
169
|
+
}
|
|
170
|
+
|
|
57
171
|
while (i < len) {
|
|
58
172
|
const char = line[i];
|
|
59
173
|
|
|
@@ -64,13 +178,66 @@ function tokenizeLine(line: string): string {
|
|
|
64
178
|
continue;
|
|
65
179
|
}
|
|
66
180
|
|
|
181
|
+
// Comment (// or /* */)
|
|
182
|
+
if (char === '/' && i + 1 < len) {
|
|
183
|
+
if (line[i + 1] === '/') {
|
|
184
|
+
// Line comment - consume rest of line
|
|
185
|
+
const comment = line.slice(i);
|
|
186
|
+
result += `<span class="token-comment">${escapeHtml(comment)}</span>`;
|
|
187
|
+
break;
|
|
188
|
+
} else if (line[i + 1] === '*') {
|
|
189
|
+
// Block comment start
|
|
190
|
+
const commentEnd = line.indexOf('*/', i + 2);
|
|
191
|
+
if (commentEnd !== -1) {
|
|
192
|
+
const comment = line.slice(i, commentEnd + 2);
|
|
193
|
+
result += `<span class="token-comment">${escapeHtml(comment)}</span>`;
|
|
194
|
+
i = commentEnd + 2;
|
|
195
|
+
continue;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// String literals (single, double, backtick)
|
|
201
|
+
if (char === '"' || char === "'" || char === '`') {
|
|
202
|
+
let j = i + 1;
|
|
203
|
+
let escaped = false;
|
|
204
|
+
|
|
205
|
+
// Find matching quote
|
|
206
|
+
while (j < len) {
|
|
207
|
+
if (escaped) {
|
|
208
|
+
escaped = false;
|
|
209
|
+
j++;
|
|
210
|
+
continue;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
if (line[j] === '\\') {
|
|
214
|
+
escaped = true;
|
|
215
|
+
j++;
|
|
216
|
+
continue;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (line[j] === char) {
|
|
220
|
+
j++;
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
j++;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const str = line.slice(i, j);
|
|
228
|
+
result += `<span class="token-string">${escapeHtml(str)}</span>`;
|
|
229
|
+
i = j;
|
|
230
|
+
continue;
|
|
231
|
+
}
|
|
232
|
+
|
|
67
233
|
// Word (identifier or keyword)
|
|
68
234
|
if (/[a-zA-Z_$]/.test(char)) {
|
|
69
235
|
const word = consumeWord(line, i);
|
|
70
236
|
const escaped = escapeHtml(word);
|
|
71
237
|
|
|
72
|
-
if (
|
|
73
|
-
|
|
238
|
+
if (ALL_KEYWORDS.has(word)) {
|
|
239
|
+
const tokenClass = getKeywordClass(word);
|
|
240
|
+
result += `<span class="${tokenClass}">${escaped}</span>`;
|
|
74
241
|
} else {
|
|
75
242
|
result += escaped;
|
|
76
243
|
}
|
|
@@ -87,6 +254,28 @@ function tokenizeLine(line: string): string {
|
|
|
87
254
|
continue;
|
|
88
255
|
}
|
|
89
256
|
|
|
257
|
+
// Operators (multi-char)
|
|
258
|
+
if (isOperator(char)) {
|
|
259
|
+
const op = consumeOperator(line, i);
|
|
260
|
+
result += `<span class="token-operator">${escapeHtml(op)}</span>`;
|
|
261
|
+
i += op.length;
|
|
262
|
+
continue;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// Structural punctuation (braces, brackets, parens)
|
|
266
|
+
if (isStructural(char)) {
|
|
267
|
+
result += `<span class="token-structural">${escapeHtml(char)}</span>`;
|
|
268
|
+
i++;
|
|
269
|
+
continue;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Delimiters (semicolon, comma, colon)
|
|
273
|
+
if (isDelimiter(char)) {
|
|
274
|
+
result += `<span class="token-delimiter">${escapeHtml(char)}</span>`;
|
|
275
|
+
i++;
|
|
276
|
+
continue;
|
|
277
|
+
}
|
|
278
|
+
|
|
90
279
|
// Everything else - just escape and append
|
|
91
280
|
result += escapeHtml(char);
|
|
92
281
|
i++;
|
|
@@ -141,7 +330,7 @@ export function renderLineWithTokens(parsedLine: ParsedLine): string {
|
|
|
141
330
|
*/
|
|
142
331
|
export function getSyntaxHighlightCSS(): string {
|
|
143
332
|
return `
|
|
144
|
-
/*
|
|
333
|
+
/* Semantic Code Highlighting */
|
|
145
334
|
.jux-code {
|
|
146
335
|
font-family: 'Fira Code', 'Consolas', 'Monaco', monospace;
|
|
147
336
|
font-size: 14px;
|
|
@@ -195,13 +384,46 @@ export function getSyntaxHighlightCSS(): string {
|
|
|
195
384
|
display: none;
|
|
196
385
|
}
|
|
197
386
|
|
|
198
|
-
/* Token colors */
|
|
199
|
-
.token-
|
|
200
|
-
color: #c678dd;
|
|
387
|
+
/* Token colors - Semantic categories */
|
|
388
|
+
.token-control {
|
|
389
|
+
color: #c678dd; /* Purple - control flow (if, for, return) */
|
|
390
|
+
font-weight: 600;
|
|
391
|
+
}
|
|
392
|
+
.token-declaration {
|
|
393
|
+
color: #61afef; /* Blue - declarations (const, let, function) */
|
|
394
|
+
font-weight: 600;
|
|
395
|
+
}
|
|
396
|
+
.token-operator-keyword {
|
|
397
|
+
color: #e06c75; /* Red - operator keywords (typeof, instanceof) */
|
|
398
|
+
font-weight: 600;
|
|
399
|
+
}
|
|
400
|
+
.token-special {
|
|
401
|
+
color: #e5c07b; /* Yellow - special keywords (this, super, async) */
|
|
201
402
|
font-weight: 600;
|
|
202
403
|
}
|
|
404
|
+
.token-jux {
|
|
405
|
+
color: #56b6c2; /* Cyan - JUX-specific (jux, state, render) */
|
|
406
|
+
font-weight: 700;
|
|
407
|
+
}
|
|
203
408
|
.token-number {
|
|
204
|
-
color: #d19a66;
|
|
409
|
+
color: #d19a66; /* Orange - numbers */
|
|
410
|
+
}
|
|
411
|
+
.token-string {
|
|
412
|
+
color: #98c379; /* Green - strings */
|
|
413
|
+
}
|
|
414
|
+
.token-comment {
|
|
415
|
+
color: #5c6370; /* Gray - comments */
|
|
416
|
+
font-style: italic;
|
|
417
|
+
}
|
|
418
|
+
.token-structural {
|
|
419
|
+
color: #abb2bf; /* Light gray - braces, brackets, parens */
|
|
420
|
+
font-weight: 600;
|
|
421
|
+
}
|
|
422
|
+
.token-delimiter {
|
|
423
|
+
color: #777; /* Dark gray - semicolons, commas, colons */
|
|
424
|
+
}
|
|
425
|
+
.token-operator {
|
|
426
|
+
color: #56b6c2; /* Cyan - operators (+, -, *, etc.) */
|
|
205
427
|
}
|
|
206
428
|
`;
|
|
207
429
|
}
|