sugar-high 0.2.1 → 0.3.1
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 +17 -2
- package/lib/index.mjs +95 -45
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
|
|
4
4
|
Super lightweight JSX syntax highlighter, around 1KB after minified and gzipped
|
|
5
5
|
|
|
6
|
-

|
|
7
7
|
|
|
8
8
|
### Usage
|
|
9
9
|
|
|
@@ -54,7 +54,22 @@ Then make your own theme with customized colors by token type and put in global
|
|
|
54
54
|
color: #a19595;
|
|
55
55
|
}
|
|
56
56
|
.sh__jsxliterals {
|
|
57
|
-
color: #
|
|
57
|
+
color: #6266d1;
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Features
|
|
62
|
+
|
|
63
|
+
#### Line number
|
|
64
|
+
|
|
65
|
+
Sugar high provide `.sh_line` class name for each line. To display line number, define the `.sh_line::before` element with CSS will enable line numbers automatically.
|
|
66
|
+
|
|
67
|
+
```css
|
|
68
|
+
.sh__line::before {
|
|
69
|
+
content: attr(data-line-number);
|
|
70
|
+
margin-right: 24px;
|
|
71
|
+
text-align: right;
|
|
72
|
+
color: #a4a4a4;
|
|
58
73
|
}
|
|
59
74
|
```
|
|
60
75
|
|
package/lib/index.mjs
CHANGED
|
@@ -71,6 +71,7 @@ const signs = new Set([
|
|
|
71
71
|
'[',
|
|
72
72
|
']',
|
|
73
73
|
'#',
|
|
74
|
+
'@',
|
|
74
75
|
'\\',
|
|
75
76
|
])
|
|
76
77
|
|
|
@@ -87,7 +88,7 @@ export const types = [
|
|
|
87
88
|
]
|
|
88
89
|
|
|
89
90
|
/**
|
|
90
|
-
*
|
|
91
|
+
*
|
|
91
92
|
* 0 - identifier
|
|
92
93
|
* 1 - keyword
|
|
93
94
|
* 2 - string
|
|
@@ -97,7 +98,7 @@ export const types = [
|
|
|
97
98
|
* 6 - break
|
|
98
99
|
* 7 - space
|
|
99
100
|
* 8 - jsx literals
|
|
100
|
-
*
|
|
101
|
+
*
|
|
101
102
|
*/
|
|
102
103
|
const [
|
|
103
104
|
T_IDENTIFIER,
|
|
@@ -122,14 +123,19 @@ function encode(str) {
|
|
|
122
123
|
.replace(/>/g, '>')
|
|
123
124
|
.replace(/"/g, '"')
|
|
124
125
|
.replace(/'/g, ''')
|
|
125
|
-
// matches space but not new line
|
|
126
|
-
.replace(/[^\S\r\n]/g, ' ')
|
|
127
126
|
}
|
|
128
127
|
|
|
129
128
|
function isWord(chr) {
|
|
130
129
|
return /^[\w_]+$/.test(chr)
|
|
131
130
|
}
|
|
132
131
|
|
|
132
|
+
function isCls(str) {
|
|
133
|
+
const chr0 = str[0]
|
|
134
|
+
return isWord(chr0) &&
|
|
135
|
+
chr0 === chr0.toUpperCase() ||
|
|
136
|
+
str === 'null'
|
|
137
|
+
}
|
|
138
|
+
|
|
133
139
|
function isIdentifierChar(chr) {
|
|
134
140
|
return /^[a-zA-Z_$]$/.test(chr)
|
|
135
141
|
}
|
|
@@ -151,8 +157,8 @@ function isRegexStart(str) {
|
|
|
151
157
|
return str[0] === '/' && !isCommentStart(str[0] + str[1])
|
|
152
158
|
}
|
|
153
159
|
|
|
154
|
-
/**
|
|
155
|
-
* @param {string} code
|
|
160
|
+
/**
|
|
161
|
+
* @param {string} code
|
|
156
162
|
* @return {Array<[number, string]>}
|
|
157
163
|
*/
|
|
158
164
|
export function tokenize(code) {
|
|
@@ -162,16 +168,22 @@ export function tokenize(code) {
|
|
|
162
168
|
/** @type {Array<[number, string]>} */
|
|
163
169
|
const tokens = []
|
|
164
170
|
|
|
165
|
-
// jsx.tag for entering open or closed
|
|
171
|
+
// jsx.tag for entering open or closed tags
|
|
172
|
+
// jsx.close for is the closing sign of open or close tags
|
|
166
173
|
// jsx.child for entering children
|
|
167
174
|
// jsx.expr for entering {expression}
|
|
168
|
-
/** @type {{ tag: boolean; child: boolean; expr: boolean }} */
|
|
169
|
-
const jsx = { tag: false, child: false, expr: false }
|
|
175
|
+
/** @type {{ tag: boolean; close: boolean; child: boolean; expr: boolean }} */
|
|
176
|
+
const jsx = { tag: false, close: false, child: false, expr: false }
|
|
170
177
|
|
|
171
|
-
const
|
|
178
|
+
const inJsxTag = () => jsx.tag || jsx.close
|
|
179
|
+
const inJsxLiterals = () => !inJsxTag() && jsx.child && !jsx.expr
|
|
172
180
|
|
|
181
|
+
/**
|
|
182
|
+
*
|
|
183
|
+
* @param {string} token
|
|
184
|
+
* @returns {number}
|
|
185
|
+
*/
|
|
173
186
|
function classify(token) {
|
|
174
|
-
const chr0 = token[0]
|
|
175
187
|
if (keywords.has(token)) {
|
|
176
188
|
return T_KEYWORD
|
|
177
189
|
} else if (token === '\n') {
|
|
@@ -180,17 +192,12 @@ export function tokenize(code) {
|
|
|
180
192
|
return T_SPACE
|
|
181
193
|
} else if (token.split('').every(ch => signs.has(ch))) {
|
|
182
194
|
return T_SIGN
|
|
183
|
-
} else if (
|
|
184
|
-
|
|
185
|
-
(
|
|
186
|
-
isWord(chr0) &&
|
|
187
|
-
chr0 === chr0.toUpperCase() ||
|
|
188
|
-
token === 'null'
|
|
189
|
-
)
|
|
190
|
-
) {
|
|
191
|
-
return T_CLS_NUMBER
|
|
195
|
+
} else if (isCls(token)) {
|
|
196
|
+
return inJsxLiterals() ? T_JSX_LITERALS : inJsxTag() ? T_IDENTIFIER : T_CLS_NUMBER
|
|
192
197
|
} else {
|
|
193
|
-
return
|
|
198
|
+
return inJsxLiterals()
|
|
199
|
+
? T_JSX_LITERALS
|
|
200
|
+
: isIdentifier(token) ? T_IDENTIFIER : T_STRING
|
|
194
201
|
}
|
|
195
202
|
}
|
|
196
203
|
|
|
@@ -210,15 +217,19 @@ export function tokenize(code) {
|
|
|
210
217
|
const curr = code[i]
|
|
211
218
|
const prev = code[i - 1]
|
|
212
219
|
const next = code[i + 1]
|
|
220
|
+
const p_c = prev + curr // previous and current
|
|
213
221
|
const c_n = curr + next // current and next
|
|
214
|
-
const p_c = prev + curr // previous and current
|
|
215
222
|
const isJsxLiterals = inJsxLiterals()
|
|
216
223
|
|
|
224
|
+
if (jsx.close && (last[1] === '>' || last[1] === '/>')) {
|
|
225
|
+
jsx.close = false
|
|
226
|
+
}
|
|
217
227
|
if (jsx.tag) {
|
|
218
|
-
const isOpenElementEnd = curr === '>'
|
|
228
|
+
const isOpenElementEnd = curr === '>'
|
|
219
229
|
const isCloseElementEnd = p_c === '/>'
|
|
220
|
-
jsx.child = !isCloseElementEnd
|
|
230
|
+
jsx.child = !isCloseElementEnd
|
|
221
231
|
jsx.tag = !(isOpenElementEnd || isCloseElementEnd)
|
|
232
|
+
jsx.close = isOpenElementEnd || isCloseElementEnd
|
|
222
233
|
}
|
|
223
234
|
// if it's not in a jsx tag declaration or a string, close child if next is jsx close tag
|
|
224
235
|
if (!jsx.tag && (curr === '<' && isIdentifierChar(next) || c_n === '</')) {
|
|
@@ -235,10 +246,10 @@ export function tokenize(code) {
|
|
|
235
246
|
// * (expr1) / expr2: `()` before `/` operator is still in expression
|
|
236
247
|
// * <non comment start>/ expr: non comment start before `/` is not regex
|
|
237
248
|
if (
|
|
238
|
-
isRegexChar &&
|
|
239
|
-
lastType &&
|
|
249
|
+
isRegexChar &&
|
|
250
|
+
lastType &&
|
|
240
251
|
!(
|
|
241
|
-
(lastType === T_SIGN && !'()'.includes(lastToken)) ||
|
|
252
|
+
(lastType === T_SIGN && !'()'.includes(lastToken)) ||
|
|
242
253
|
lastType === T_COMMENT
|
|
243
254
|
)
|
|
244
255
|
) {
|
|
@@ -247,14 +258,18 @@ export function tokenize(code) {
|
|
|
247
258
|
continue
|
|
248
259
|
}
|
|
249
260
|
const start = i++
|
|
261
|
+
const leftQuote = code[start]
|
|
250
262
|
let foundClose = false
|
|
251
|
-
|
|
263
|
+
|
|
252
264
|
// end of line of end of file
|
|
253
|
-
const
|
|
265
|
+
const isEof = () => i >= code.length
|
|
266
|
+
const isEol = () => isEof() || code[i] === '\n'
|
|
254
267
|
// string quotation
|
|
255
268
|
if (isQuotationChar) {
|
|
256
|
-
for (; !
|
|
257
|
-
|
|
269
|
+
for (; !isEof(); i++) {
|
|
270
|
+
// handle single quotes
|
|
271
|
+
if ((leftQuote === "'" || leftQuote === '"') && code[i] === '\n') break
|
|
272
|
+
if (isStringQuotation(code[i]) && code[i] === leftQuote) {
|
|
258
273
|
foundClose = true
|
|
259
274
|
break
|
|
260
275
|
}
|
|
@@ -267,7 +282,7 @@ export function tokenize(code) {
|
|
|
267
282
|
// append regex flags
|
|
268
283
|
while (start !== i && /^[a-z]$/.test(code[i + 1]) && !isEol()) {
|
|
269
284
|
i++
|
|
270
|
-
}
|
|
285
|
+
}
|
|
271
286
|
break
|
|
272
287
|
}
|
|
273
288
|
}
|
|
@@ -284,6 +299,7 @@ export function tokenize(code) {
|
|
|
284
299
|
i = start
|
|
285
300
|
}
|
|
286
301
|
} else if (isCommentStart(c_n)) {
|
|
302
|
+
append()
|
|
287
303
|
const start = i
|
|
288
304
|
if (next === '/') {
|
|
289
305
|
for (; i < code.length && code[i] !== '\n'; i++);
|
|
@@ -313,6 +329,9 @@ export function tokenize(code) {
|
|
|
313
329
|
!signs.has(curr)
|
|
314
330
|
) {
|
|
315
331
|
current += curr
|
|
332
|
+
if (next === '<') {
|
|
333
|
+
append();
|
|
334
|
+
}
|
|
316
335
|
} else {
|
|
317
336
|
append()
|
|
318
337
|
current = curr
|
|
@@ -338,30 +357,61 @@ export function tokenize(code) {
|
|
|
338
357
|
return tokens
|
|
339
358
|
}
|
|
340
359
|
|
|
341
|
-
/**
|
|
342
|
-
* @param {Array<[number, string]>} tokens
|
|
360
|
+
/**
|
|
361
|
+
* @param {Array<[number, string]>} tokens
|
|
343
362
|
* @return {Array<string>}
|
|
344
363
|
*/
|
|
345
364
|
function generate(tokens) {
|
|
346
|
-
|
|
365
|
+
let lineNo = 1
|
|
366
|
+
const linesHtml = []
|
|
367
|
+
const createLine = (content) => `<span data-line-number="${lineNo++}" class="sh__line">${content}</span>`
|
|
368
|
+
|
|
369
|
+
function flushLine(tokens) {
|
|
370
|
+
linesHtml.push(createLine(
|
|
371
|
+
tokens.map(token => (
|
|
372
|
+
`<span class="sh__${types[token[0]]}">${encode(token[1])}</span>`
|
|
373
|
+
))
|
|
374
|
+
.join('')
|
|
375
|
+
))
|
|
376
|
+
}
|
|
377
|
+
const lineTokens = []
|
|
347
378
|
for (let i = 0; i < tokens.length; i++) {
|
|
348
|
-
const
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
379
|
+
const token = tokens[i]
|
|
380
|
+
const [type, value] = token
|
|
381
|
+
if (type !== T_BREAK) {
|
|
382
|
+
// Divide multi-line token into multi-line code
|
|
383
|
+
if (value.includes('\n')) {
|
|
384
|
+
const lines = value.split('\n')
|
|
385
|
+
for (let j = 0; j < lines.length; j++) {
|
|
386
|
+
lineTokens.push([type, lines[j]])
|
|
387
|
+
if (j < lines.length - 1) {
|
|
388
|
+
flushLine(lineTokens)
|
|
389
|
+
lineTokens.length = 0
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
} else {
|
|
393
|
+
lineTokens.push(token)
|
|
394
|
+
}
|
|
395
|
+
} else {
|
|
396
|
+
lineTokens.push([type, ''])
|
|
397
|
+
flushLine(lineTokens)
|
|
398
|
+
lineTokens.length = 0
|
|
399
|
+
}
|
|
354
400
|
}
|
|
355
|
-
|
|
401
|
+
|
|
402
|
+
if (lineTokens.length)
|
|
403
|
+
flushLine(lineTokens)
|
|
404
|
+
|
|
405
|
+
return linesHtml
|
|
356
406
|
}
|
|
357
407
|
|
|
358
408
|
/**
|
|
359
|
-
*
|
|
360
|
-
* @param {string} code
|
|
409
|
+
*
|
|
410
|
+
* @param {string} code
|
|
361
411
|
* @returns {string}
|
|
362
412
|
*/
|
|
363
413
|
export function highlight(code) {
|
|
364
414
|
const tokens = tokenize(code)
|
|
365
|
-
const output = generate(tokens).join('')
|
|
415
|
+
const output = generate(tokens).join('\n')
|
|
366
416
|
return output
|
|
367
417
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sugar-high",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"exports": "./lib/index.mjs",
|
|
6
6
|
"description": "Super lightweight JSX syntax highlighter",
|
|
@@ -16,9 +16,9 @@
|
|
|
16
16
|
"build": "next build docs"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"next": "12.1.
|
|
20
|
-
"react": "18.0.0
|
|
21
|
-
"react-dom": "18.0.0
|
|
19
|
+
"next": "12.1.4",
|
|
20
|
+
"react": "18.0.0",
|
|
21
|
+
"react-dom": "18.0.0",
|
|
22
22
|
"vitest": "~0.6.0"
|
|
23
23
|
}
|
|
24
24
|
}
|