sugar-high 2.5.0 → 2.5.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 +22 -22
- package/lib/lang/css.js +31 -9
- package/lib/lang/embedded-html.js +21 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -31,28 +31,6 @@ import { highlight } from 'sugar-high'
|
|
|
31
31
|
const html = highlight('const ready = true')
|
|
32
32
|
```
|
|
33
33
|
|
|
34
|
-
## Experimental WebGPU highlighting
|
|
35
|
-
|
|
36
|
-
Use the opt-in `sugar-high/gpu` entry for asynchronous, language-agnostic highlighting powered by
|
|
37
|
-
[`gpu-lexer`](https://gpu-lexer.vercel.app). It requires WebGPU and rejects when WebGPU is
|
|
38
|
-
unavailable; importing any other Sugar High entry does not load the GPU model.
|
|
39
|
-
|
|
40
|
-
Install the experimental lexer separately when using this entry:
|
|
41
|
-
|
|
42
|
-
```sh
|
|
43
|
-
npm install sugar-high gpu-lexer
|
|
44
|
-
```
|
|
45
|
-
|
|
46
|
-
```js
|
|
47
|
-
import { highlight, parse } from 'sugar-high/gpu'
|
|
48
|
-
|
|
49
|
-
const parsed = await parse(source)
|
|
50
|
-
const html = await highlight(source)
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
GPU labels map onto Sugar High's existing token classes, themes, `cx`, `mark`, and `markLine`
|
|
54
|
-
hooks. The model infers syntax without a language option and may differ from grammar-based output.
|
|
55
|
-
|
|
56
34
|
JavaScript, including JSX, is the default. Pass a canonical name for another built-in language:
|
|
57
35
|
|
|
58
36
|
```js
|
|
@@ -241,6 +219,28 @@ See [`docs/API.md`](https://github.com/huozhi/sugar-high/blob/main/docs/API.md)
|
|
|
241
219
|
options, and lower-level functions. Upgrading from v1? Read the
|
|
242
220
|
[v2 migration guide](https://github.com/huozhi/sugar-high/blob/main/docs/MIGRATION.md).
|
|
243
221
|
|
|
222
|
+
## Experimental WebGPU highlighting
|
|
223
|
+
|
|
224
|
+
Use the opt-in `sugar-high/gpu` entry for asynchronous, language-agnostic highlighting powered by
|
|
225
|
+
[`gpu-lexer`](https://gpu-lexer.vercel.app). It requires WebGPU and rejects when WebGPU is
|
|
226
|
+
unavailable; importing any other Sugar High entry does not load the GPU model.
|
|
227
|
+
|
|
228
|
+
Install the experimental lexer separately when using this entry:
|
|
229
|
+
|
|
230
|
+
```sh
|
|
231
|
+
npm install sugar-high gpu-lexer
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
```js
|
|
235
|
+
import { highlight, parse } from 'sugar-high/gpu'
|
|
236
|
+
|
|
237
|
+
const parsed = await parse(source)
|
|
238
|
+
const html = await highlight(source)
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
GPU labels map onto Sugar High's existing token classes, themes, `cx`, `mark`, and `markLine`
|
|
242
|
+
hooks. The model infers syntax without a language option and may differ from grammar-based output.
|
|
243
|
+
|
|
244
244
|
## Benchmarks
|
|
245
245
|
|
|
246
246
|
Sugar High, PrismJS, and highlight.js highlighting the same generated TypeScript files:
|
package/lib/lang/css.js
CHANGED
|
@@ -36,17 +36,23 @@ const isHyphen = ([type, value]) => type === T_SIGN && value === '-'
|
|
|
36
36
|
* @param {Array<[number, string]>} tokens
|
|
37
37
|
*/
|
|
38
38
|
const mergeDashedNames = (tokens) => {
|
|
39
|
+
/** @type {Array<[number, string]>} */
|
|
40
|
+
const merged = []
|
|
39
41
|
for (let index = 0; index < tokens.length; index++) {
|
|
40
42
|
let firstWord = index
|
|
41
43
|
let end = index
|
|
42
44
|
|
|
43
45
|
if (isHyphen(tokens[end])) {
|
|
44
46
|
while (tokens[end] && isHyphen(tokens[end])) end++
|
|
45
|
-
if (!tokens[end] || !isNameStart(tokens[end]))
|
|
47
|
+
if (!tokens[end] || !isNameStart(tokens[end])) {
|
|
48
|
+
merged.push(tokens[index])
|
|
49
|
+
continue
|
|
50
|
+
}
|
|
46
51
|
firstWord = end++
|
|
47
52
|
} else if (isNameStart(tokens[end])) {
|
|
48
53
|
end++
|
|
49
54
|
} else {
|
|
55
|
+
merged.push(tokens[index])
|
|
50
56
|
continue
|
|
51
57
|
}
|
|
52
58
|
|
|
@@ -62,10 +68,15 @@ const mergeDashedNames = (tokens) => {
|
|
|
62
68
|
end++
|
|
63
69
|
}
|
|
64
70
|
|
|
65
|
-
if (!dashed)
|
|
71
|
+
if (!dashed) {
|
|
72
|
+
merged.push(tokens[index])
|
|
73
|
+
continue
|
|
74
|
+
}
|
|
66
75
|
const name = tokens.slice(index, end).map(([, value]) => value).join('')
|
|
67
|
-
|
|
76
|
+
merged.push([tokens[firstWord][0], name])
|
|
77
|
+
index = end - 1
|
|
68
78
|
}
|
|
79
|
+
return merged
|
|
69
80
|
}
|
|
70
81
|
|
|
71
82
|
/** Return true when a colon belongs to a nested selector instead of a declaration. */
|
|
@@ -91,29 +102,37 @@ const opensBlock = (tokens, start) => {
|
|
|
91
102
|
* @param {import('../core.js').ParseOptions} options
|
|
92
103
|
*/
|
|
93
104
|
export const tokenize = (code, options) => {
|
|
94
|
-
const tokens = tokenizePlain(code, { ...options, tokenize: undefined })
|
|
95
|
-
|
|
105
|
+
const tokens = mergeDashedNames(tokenizePlain(code, { ...options, tokenize: undefined }))
|
|
106
|
+
/** @type {Array<[number, string]>} */
|
|
107
|
+
const output = []
|
|
96
108
|
let blockDepth = 0
|
|
97
109
|
let declarationStart = false
|
|
98
110
|
|
|
99
111
|
for (let index = 0; index < tokens.length; index++) {
|
|
100
|
-
const
|
|
112
|
+
const token = tokens[index]
|
|
113
|
+
const [type, value] = token
|
|
101
114
|
|
|
102
115
|
if (type === T_SIGN && value === '{') {
|
|
103
116
|
blockDepth++
|
|
104
117
|
declarationStart = true
|
|
118
|
+
output.push(token)
|
|
105
119
|
continue
|
|
106
120
|
}
|
|
107
121
|
if (type === T_SIGN && value === '}') {
|
|
108
122
|
blockDepth--
|
|
109
123
|
declarationStart = false
|
|
124
|
+
output.push(token)
|
|
110
125
|
continue
|
|
111
126
|
}
|
|
112
127
|
if (type === T_SIGN && value === ';') {
|
|
113
128
|
declarationStart = blockDepth > 0
|
|
129
|
+
output.push(token)
|
|
130
|
+
continue
|
|
131
|
+
}
|
|
132
|
+
if (!declarationStart || isIgnored(type)) {
|
|
133
|
+
output.push(token)
|
|
114
134
|
continue
|
|
115
135
|
}
|
|
116
|
-
if (!declarationStart || isIgnored(type)) continue
|
|
117
136
|
|
|
118
137
|
const propertyStart = index
|
|
119
138
|
let propertyEnd = index
|
|
@@ -133,10 +152,13 @@ export const tokenize = (code, options) => {
|
|
|
133
152
|
.slice(propertyStart, propertyEnd)
|
|
134
153
|
.map(([, part]) => part)
|
|
135
154
|
.join('')
|
|
136
|
-
|
|
155
|
+
output.push([T_PROPERTY, property])
|
|
156
|
+
index = propertyEnd - 1
|
|
157
|
+
} else {
|
|
158
|
+
output.push(token)
|
|
137
159
|
}
|
|
138
160
|
declarationStart = false
|
|
139
161
|
}
|
|
140
162
|
|
|
141
|
-
return
|
|
163
|
+
return output
|
|
142
164
|
}
|
|
@@ -35,6 +35,8 @@ function findTagEnd(code, start) {
|
|
|
35
35
|
/** Find real script/style blocks while ignoring comments and quoted attributes. */
|
|
36
36
|
function findEmbeddedBlocks(code) {
|
|
37
37
|
const blocks = []
|
|
38
|
+
/** @type {Set<string>} */
|
|
39
|
+
const unclosed = new Set()
|
|
38
40
|
let index = 0
|
|
39
41
|
while (index < code.length) {
|
|
40
42
|
if (code.startsWith('<!--', index)) {
|
|
@@ -57,10 +59,15 @@ function findEmbeddedBlocks(code) {
|
|
|
57
59
|
}
|
|
58
60
|
|
|
59
61
|
const tag = match[1].toLowerCase()
|
|
62
|
+
if (unclosed.has(tag)) {
|
|
63
|
+
index = openEnd
|
|
64
|
+
continue
|
|
65
|
+
}
|
|
60
66
|
const closing = embeddedClosingTag(tag)
|
|
61
67
|
closing.lastIndex = openEnd
|
|
62
68
|
const closeMatch = closing.exec(code)
|
|
63
69
|
if (!closeMatch) {
|
|
70
|
+
unclosed.add(tag)
|
|
64
71
|
index = openEnd
|
|
65
72
|
continue
|
|
66
73
|
}
|
|
@@ -77,22 +84,29 @@ function findEmbeddedBlocks(code) {
|
|
|
77
84
|
* @returns {Array<[number, string]>}
|
|
78
85
|
*/
|
|
79
86
|
export function tokenizeEmbeddedHtml(code) {
|
|
87
|
+
const blocks = findEmbeddedBlocks(code)
|
|
88
|
+
if (!blocks.length) return tokenizeHtml(code)
|
|
89
|
+
|
|
80
90
|
/** @type {Array<[number, string]>} */
|
|
81
91
|
const tokens = []
|
|
92
|
+
/** @param {Array<[number, string]>} part */
|
|
93
|
+
const append = (part) => {
|
|
94
|
+
for (let index = 0; index < part.length; index++) tokens.push(part[index])
|
|
95
|
+
}
|
|
82
96
|
let cursor = 0
|
|
83
97
|
|
|
84
|
-
for (const block of
|
|
98
|
+
for (const block of blocks) {
|
|
85
99
|
const { start, openEnd, closeStart, end, tag } = block
|
|
86
100
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
101
|
+
append(tokenizeHtml(code.slice(cursor, start)))
|
|
102
|
+
append(tokenizeHtml(code.slice(start, openEnd)))
|
|
103
|
+
append(tag === 'style'
|
|
90
104
|
? tokenizeCss(code.slice(openEnd, closeStart))
|
|
91
|
-
: tokenizeJavaScript(code.slice(openEnd, closeStart), { jsx: false }))
|
|
92
|
-
|
|
105
|
+
: tokenizeJavaScript(code.slice(openEnd, closeStart), { jsx: false }))
|
|
106
|
+
append(tokenizeHtml(code.slice(closeStart, end)))
|
|
93
107
|
cursor = end
|
|
94
108
|
}
|
|
95
109
|
|
|
96
|
-
|
|
110
|
+
append(tokenizeHtml(code.slice(cursor)))
|
|
97
111
|
return tokens
|
|
98
112
|
}
|