sugar-high 1.2.1 → 2.0.0
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 +137 -42
- package/lib/core.d.ts +117 -0
- package/lib/core.js +202 -0
- package/lib/index.d.ts +39 -22
- package/lib/index.js +15 -982
- package/lib/lang.d.ts +14 -0
- package/lib/lang.js +132 -0
- package/lib/presets/lang/cpp.js +22 -0
- package/lib/presets/lang/csharp.js +21 -0
- package/lib/presets/lang/css.js +5 -0
- package/lib/presets/lang/diff.js +12 -6
- package/lib/presets/lang/dockerfile.js +5 -0
- package/lib/presets/lang/graphql.js +5 -0
- package/lib/presets/lang/hash-comment-base.js +7 -0
- package/lib/presets/lang/hcl.js +4 -0
- package/lib/presets/lang/html.js +14 -0
- package/lib/presets/lang/javascript-runtime.js +781 -0
- package/lib/presets/lang/javascript.js +6 -0
- package/lib/presets/lang/json.js +8 -0
- package/lib/presets/lang/kotlin.js +5 -0
- package/lib/presets/lang/markdown.js +15 -0
- package/lib/presets/lang/php.js +5 -0
- package/lib/presets/lang/plain-base.js +4 -0
- package/lib/presets/lang/powershell.js +5 -0
- package/lib/presets/lang/shell.js +10 -0
- package/lib/presets/lang/sql.js +31 -0
- package/lib/presets/lang/swift.js +5 -0
- package/lib/presets/lang/toml.js +5 -0
- package/lib/presets/lang/typescript.js +7 -0
- package/lib/presets/lang/yaml.js +10 -0
- package/lib/shared.js +149 -0
- package/package.json +15 -10
- package/lib/presets/index.d.ts +0 -16
- package/lib/presets/index.js +0 -7
package/README.md
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
# Sugar High
|
|
2
2
|
|
|
3
|
-
[![
|
|
3
|
+
[![version][npm-version-badge]][npm]
|
|
4
|
+
[![downloads][npm-downloads-badge]][npm]
|
|
4
5
|
|
|
5
|
-
|
|
6
|
+
Lightweight, zero-dependency syntax highlighting for JavaScript, popular programming languages,
|
|
7
|
+
and formats commonly generated by coding agents. It runs in browsers and JavaScript runtimes and
|
|
8
|
+
returns HTML without requiring a DOM.
|
|
6
9
|
|
|
7
10
|

|
|
8
11
|
|
|
@@ -12,39 +15,128 @@ Super lightweight syntax highlighter for JavaScript and JSX—about **1 kB** min
|
|
|
12
15
|
npm install sugar-high
|
|
13
16
|
```
|
|
14
17
|
|
|
15
|
-
##
|
|
18
|
+
## Highlight code
|
|
16
19
|
|
|
17
20
|
```js
|
|
18
21
|
import { highlight } from 'sugar-high'
|
|
19
22
|
|
|
20
|
-
const
|
|
23
|
+
const html = highlight('const ready = true')
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
JavaScript, including JSX, is the default. Pass a canonical name for another built-in language:
|
|
21
27
|
|
|
22
|
-
|
|
28
|
+
```js
|
|
29
|
+
highlight('print("hi")', { lang: 'python' })
|
|
30
|
+
highlight('{"ready": true}', { lang: 'json' })
|
|
31
|
+
highlight('+ added', { lang: 'diff' })
|
|
23
32
|
```
|
|
24
33
|
|
|
25
|
-
|
|
34
|
+
The `lang` option is typed and accepts canonical names only.
|
|
35
|
+
|
|
36
|
+
## Advanced: normalize extensions and aliases
|
|
26
37
|
|
|
27
|
-
|
|
38
|
+
You do not need `lang()` when the language is already known. Use it only when input comes from a
|
|
39
|
+
filename extension, Markdown fence, or another integration. It converts aliases to the canonical
|
|
40
|
+
name expected by `highlight`:
|
|
28
41
|
|
|
29
42
|
```js
|
|
30
|
-
import {
|
|
31
|
-
import { rust } from 'sugar-high/presets'
|
|
43
|
+
import { lang } from 'sugar-high/lang'
|
|
32
44
|
|
|
33
|
-
|
|
45
|
+
lang('py') // 'python'
|
|
46
|
+
lang('bash') // 'shell'
|
|
47
|
+
lang('jsonc') // 'json'
|
|
48
|
+
lang('.yml') // 'yaml'
|
|
34
49
|
```
|
|
35
50
|
|
|
36
|
-
|
|
51
|
+
If you already have a canonical name, pass it directly—`lang()` is not required.
|
|
37
52
|
|
|
38
|
-
|
|
53
|
+
See [the API reference](../../docs/API.md#lang-mapping) for the complete mapping and normalization
|
|
54
|
+
behavior.
|
|
39
55
|
|
|
40
|
-
##
|
|
56
|
+
## Built-in languages
|
|
57
|
+
|
|
58
|
+
`javascript`, `typescript`, `css`, `python`, `c`, `go`, `java`, `rust`, `json`, `diff`, `shell`,
|
|
59
|
+
`cpp`, `csharp`, `sql`, `html`, `yaml`, `markdown`, `kotlin`, `swift`, `php`, `toml`, `powershell`,
|
|
60
|
+
`dockerfile`, `graphql`, and `hcl`.
|
|
61
|
+
|
|
62
|
+
Related dialects share one implementation: JavaScript includes JSX, TypeScript includes TSX, JSON
|
|
63
|
+
includes JSONC comments, Shell includes sh/Bash/Zsh, and HCL includes Terraform.
|
|
64
|
+
|
|
65
|
+
## Composable core
|
|
66
|
+
|
|
67
|
+
Use `sugar-high/core` to separate configurable syntax parsing from HTML rendering. It does not
|
|
68
|
+
include the built-in language registry:
|
|
69
|
+
|
|
70
|
+
```js
|
|
71
|
+
import { parse, render } from 'sugar-high/core'
|
|
72
|
+
|
|
73
|
+
const parsed = parse('select * from users', {
|
|
74
|
+
keywords: new Set(['select', 'from', 'where']),
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const html = render(parsed, {
|
|
78
|
+
cx: { keyword: 'font-bold' },
|
|
79
|
+
})
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
Use the default `sugar-high` export when you want built-in languages and one-step `highlight()`.
|
|
83
|
+
|
|
84
|
+
## Customize tokens
|
|
85
|
+
|
|
86
|
+
Use `cx` for a class map. It works well with utility CSS, CSS Modules, and global styles while
|
|
87
|
+
preserving Sugar High's semantic token classes.
|
|
88
|
+
|
|
89
|
+
```js
|
|
90
|
+
highlight(source, {
|
|
91
|
+
lang: 'typescript',
|
|
92
|
+
cx: {
|
|
93
|
+
keyword: 'font-bold',
|
|
94
|
+
comment: 'italic opacity-60',
|
|
95
|
+
},
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Use `mark(token)` for conditional classes, inline styles, or custom attributes. It mutates the
|
|
100
|
+
token and returns nothing:
|
|
101
|
+
|
|
102
|
+
```js
|
|
103
|
+
highlight(source, {
|
|
104
|
+
mark(token) {
|
|
105
|
+
if (token.type === 'comment' && token.value.includes('TODO')) {
|
|
106
|
+
token.className += ' text-orange-500'
|
|
107
|
+
token.properties['data-todo'] = true
|
|
108
|
+
}
|
|
109
|
+
},
|
|
110
|
+
})
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
`cx` runs before `mark`, so `mark` receives the composed class name.
|
|
41
114
|
|
|
42
|
-
|
|
115
|
+
## Highlight lines
|
|
43
116
|
|
|
44
|
-
|
|
117
|
+
Use `markLine` to customize generated lines. Its index is zero-based:
|
|
118
|
+
|
|
119
|
+
```js
|
|
120
|
+
highlight(source, {
|
|
121
|
+
markLine(line) {
|
|
122
|
+
if (line.index === 1) {
|
|
123
|
+
line.className += ' sh__line--highlighted'
|
|
124
|
+
}
|
|
125
|
+
},
|
|
126
|
+
})
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
Style the class with CSS. The React package also provides the one-based
|
|
130
|
+
`highlightLines={[1, [4, 7]]}` prop, while the Remark plugin reads ranges from fence metadata such
|
|
131
|
+
as `{2,5-7}`.
|
|
132
|
+
|
|
133
|
+
## Styling
|
|
134
|
+
|
|
135
|
+
Each line uses `.sh__line`. Token colors use CSS custom properties, so a theme can be embedded in
|
|
136
|
+
your own stylesheet or scoped to any ancestor:
|
|
45
137
|
|
|
46
138
|
```css
|
|
47
|
-
|
|
139
|
+
.code {
|
|
48
140
|
--sh-class: #2d5e9d;
|
|
49
141
|
--sh-identifier: #354150;
|
|
50
142
|
--sh-sign: #8996a3;
|
|
@@ -57,50 +149,52 @@ Example theme:
|
|
|
57
149
|
}
|
|
58
150
|
```
|
|
59
151
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
Use a `::before` counter on `.sh__line` for gutter numbers:
|
|
152
|
+
Lines can be styled or numbered with ordinary CSS:
|
|
63
153
|
|
|
64
154
|
```css
|
|
65
155
|
pre code {
|
|
66
|
-
counter-reset:
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
.sh__line::before {
|
|
70
|
-
counter-increment: sh-line-number 1;
|
|
71
|
-
content: counter(sh-line-number);
|
|
72
|
-
margin-right: 24px;
|
|
73
|
-
text-align: right;
|
|
74
|
-
color: #a4a4a4;
|
|
156
|
+
counter-reset: line;
|
|
75
157
|
}
|
|
76
|
-
```
|
|
77
158
|
|
|
78
|
-
### Highlighting a line
|
|
79
|
-
|
|
80
|
-
Target a line with `.sh__line:nth-child(<n>)` (1-based):
|
|
81
|
-
|
|
82
|
-
```css
|
|
83
159
|
.sh__line {
|
|
84
160
|
display: block;
|
|
85
|
-
padding: 0 12px;
|
|
86
|
-
margin: 0 -12px;
|
|
87
|
-
min-height: 1rem;
|
|
88
161
|
}
|
|
89
162
|
|
|
90
|
-
.sh__line
|
|
91
|
-
|
|
163
|
+
.sh__line::before {
|
|
164
|
+
counter-increment: line;
|
|
165
|
+
content: counter(line);
|
|
166
|
+
margin-right: 1.5rem;
|
|
167
|
+
color: #a4a4a4;
|
|
92
168
|
}
|
|
93
169
|
|
|
170
|
+
.sh__line:nth-child(5),
|
|
94
171
|
.sh__line--highlighted {
|
|
95
172
|
background: #fff8c5;
|
|
96
173
|
}
|
|
97
174
|
```
|
|
98
175
|
|
|
99
|
-
|
|
176
|
+
## React
|
|
177
|
+
|
|
178
|
+
[`@sugar-high/react`](https://sugar-high.vercel.app/react) provides a highlighted `<Code />` block
|
|
179
|
+
and textarea-overlay `<Editor />` as separate composable exports.
|
|
180
|
+
|
|
181
|
+
```tsx
|
|
182
|
+
import { Code, Editor } from '@sugar-high/react'
|
|
183
|
+
|
|
184
|
+
<Editor lang="typescript" value={source} onChange={setSource} />
|
|
185
|
+
<Code lang="typescript" lineNumbers>{source}</Code>
|
|
186
|
+
```
|
|
100
187
|
|
|
101
188
|
## Remark
|
|
102
189
|
|
|
103
|
-
|
|
190
|
+
[`@sugar-high/remark`](https://sugar-high.vercel.app/remark) highlights fenced code blocks while
|
|
191
|
+
processing Markdown. Fence aliases are normalized through the same `lang()` mapping.
|
|
192
|
+
|
|
193
|
+
## API
|
|
194
|
+
|
|
195
|
+
See [`docs/API.md`](https://github.com/huozhi/sugar-high/blob/main/docs/API.md) for package exports, the full language mapping, highlighting
|
|
196
|
+
options, and lower-level functions. Upgrading from v1? Read the
|
|
197
|
+
[v2 migration guide](https://github.com/huozhi/sugar-high/blob/main/docs/MIGRATION.md).
|
|
104
198
|
|
|
105
199
|
## License
|
|
106
200
|
|
|
@@ -108,6 +202,7 @@ MIT
|
|
|
108
202
|
|
|
109
203
|
<!-- Definitions -->
|
|
110
204
|
|
|
111
|
-
[npm-badge]: https://img.shields.io/npm/v/sugar-high
|
|
205
|
+
[npm-version-badge]: https://img.shields.io/npm/v/sugar-high?style=flat&colorA=000000&colorB=000000
|
|
206
|
+
[npm-downloads-badge]: https://img.shields.io/npm/dt/sugar-high.svg?style=flat&colorA=000000&colorB=000000
|
|
112
207
|
|
|
113
208
|
[npm]: https://www.npmjs.com/package/sugar-high
|
package/lib/core.d.ts
ADDED
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export type TokenType =
|
|
2
|
+
| 'identifier'
|
|
3
|
+
| 'keyword'
|
|
4
|
+
| 'string'
|
|
5
|
+
| 'class'
|
|
6
|
+
| 'property'
|
|
7
|
+
| 'entity'
|
|
8
|
+
| 'jsxliterals'
|
|
9
|
+
| 'sign'
|
|
10
|
+
| 'comment'
|
|
11
|
+
| 'break'
|
|
12
|
+
| 'space'
|
|
13
|
+
|
|
14
|
+
export type ParsedToken = {
|
|
15
|
+
type: TokenType
|
|
16
|
+
value: string
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export type MarkToken = {
|
|
20
|
+
type: TokenType
|
|
21
|
+
value: string
|
|
22
|
+
className: string
|
|
23
|
+
style: Record<string, string | number>
|
|
24
|
+
properties: Record<string, string | number | boolean>
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export type ParsedLine = {
|
|
28
|
+
readonly index: number
|
|
29
|
+
readonly value: string
|
|
30
|
+
readonly tokens: readonly ParsedToken[]
|
|
31
|
+
readonly annotations: readonly string[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export type AnnotateLine = {
|
|
35
|
+
readonly index: number
|
|
36
|
+
readonly value: string
|
|
37
|
+
readonly tokens: readonly ParsedToken[]
|
|
38
|
+
annotations: string[]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export type MarkLine = {
|
|
42
|
+
readonly index: number
|
|
43
|
+
readonly value: string
|
|
44
|
+
readonly tokens: readonly ParsedToken[]
|
|
45
|
+
readonly annotations: readonly string[]
|
|
46
|
+
className: string
|
|
47
|
+
style: Record<string, string | number>
|
|
48
|
+
properties: Record<string, string | number | boolean>
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export type ParsedCode = {
|
|
52
|
+
readonly value: string
|
|
53
|
+
readonly lines: readonly ParsedLine[]
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export type GeneratedProperties = {
|
|
57
|
+
className: string
|
|
58
|
+
style: Record<string, string | number>
|
|
59
|
+
[name: string]: unknown
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export type GeneratedText = {
|
|
63
|
+
type: 'text'
|
|
64
|
+
value: string
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
export type GeneratedToken = {
|
|
68
|
+
type: 'element'
|
|
69
|
+
tokenType: TokenType
|
|
70
|
+
tagName: 'span'
|
|
71
|
+
children: GeneratedText[]
|
|
72
|
+
properties: GeneratedProperties
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
export type GeneratedLine = {
|
|
76
|
+
type: 'element'
|
|
77
|
+
tagName: 'span'
|
|
78
|
+
children: GeneratedToken[]
|
|
79
|
+
properties: GeneratedProperties
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export type DisplayOptions = {
|
|
83
|
+
cx?: Partial<Record<TokenType, string>>
|
|
84
|
+
mark?: (token: MarkToken) => void
|
|
85
|
+
markLine?: (line: MarkLine) => void
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type ParseOptions = {
|
|
89
|
+
keywords?: Set<string>
|
|
90
|
+
typeKeywords?: Set<string>
|
|
91
|
+
onCommentStart?: (curr: string, next: string, index: number, code: string) => number | boolean
|
|
92
|
+
onCommentEnd?: (prev: string, curr: string, index: number, code: string) => number | boolean
|
|
93
|
+
onLiteral?: (curr: string, index: number, code: string) => number | null | undefined
|
|
94
|
+
onQuote?: (curr: string, index: number, code: string) => number | null | undefined
|
|
95
|
+
quotedKeys?: boolean
|
|
96
|
+
jsx?: boolean
|
|
97
|
+
regex?: boolean
|
|
98
|
+
templateStrings?: boolean
|
|
99
|
+
caseInsensitive?: boolean
|
|
100
|
+
typescript?: boolean
|
|
101
|
+
tokenize?: (code: string, options: ParseOptions) => Array<[number, string]>
|
|
102
|
+
/** Apply syntax-specific semantic annotations while parsing. */
|
|
103
|
+
annotateLine?: (line: AnnotateLine) => void
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function parse(code: string, options?: ParseOptions): ParsedCode
|
|
107
|
+
export function render(parsed: ParsedCode, options?: DisplayOptions): string
|
|
108
|
+
|
|
109
|
+
/** Low-level token API used by language presets and integrations. */
|
|
110
|
+
export function tokenize(code: string, options?: ParseOptions): Array<[number, string]>
|
|
111
|
+
/** Build renderable line nodes for HTML, React, and syntax-tree integrations. */
|
|
112
|
+
export function generate(parsed: ParsedCode, options?: DisplayOptions): GeneratedLine[]
|
|
113
|
+
|
|
114
|
+
export const SugarHigh: {
|
|
115
|
+
TokenTypes: { [key: number]: string }
|
|
116
|
+
TokenMap: Map<string, number>
|
|
117
|
+
}
|
package/lib/core.js
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
assemble, generate, SugarHigh, toHtml, T_BREAK, T_CLASS, T_COMMENT, T_IDENTIFIER,
|
|
5
|
+
T_KEYWORD, T_PROPERTY, T_SIGN, T_SPACE, T_STRING,
|
|
6
|
+
} from './shared.js'
|
|
7
|
+
|
|
8
|
+
const signs = new Set('+-*/%=!&|^~?:.,;()[]{}<>#@\\'.split(''))
|
|
9
|
+
const noComment = () => 0
|
|
10
|
+
|
|
11
|
+
/** @param {string} value */
|
|
12
|
+
const isWord = (value) => value === '_' || value === '$' || /[\p{L}\p{N}]/u.test(value)
|
|
13
|
+
|
|
14
|
+
/** @param {string} code @param {number} index */
|
|
15
|
+
function isQuotedKey(code, index) {
|
|
16
|
+
while (index < code.length && /\s/.test(code[index])) index++
|
|
17
|
+
return code[index] === ':'
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* General-purpose lexer for keyword, string, comment, and punctuation based languages.
|
|
22
|
+
* Complex language presets may supply `tokenize` to replace this stage.
|
|
23
|
+
* @param {string} code
|
|
24
|
+
* @param {HighlightOptions | undefined} options
|
|
25
|
+
* @returns {Array<[number, string]>}
|
|
26
|
+
*/
|
|
27
|
+
function tokenize(code, options) {
|
|
28
|
+
if (typeof options?.tokenize === 'function') return options.tokenize(code, options)
|
|
29
|
+
|
|
30
|
+
const keywords = options?.keywords || new Set()
|
|
31
|
+
const typeKeywords = options?.typeKeywords || new Set()
|
|
32
|
+
const onCommentStart = options?.onCommentStart || noComment
|
|
33
|
+
const onCommentEnd = options?.onCommentEnd || noComment
|
|
34
|
+
const normalize = options?.caseInsensitive
|
|
35
|
+
? (value) => value.toLowerCase()
|
|
36
|
+
: (value) => value
|
|
37
|
+
/** @type {Array<[number, string]>} */
|
|
38
|
+
const tokens = []
|
|
39
|
+
let lastSignificant = ''
|
|
40
|
+
|
|
41
|
+
/** @param {number} type @param {string} value */
|
|
42
|
+
function append(type, value) {
|
|
43
|
+
if (!value) return
|
|
44
|
+
tokens.push([type, value])
|
|
45
|
+
if (type !== T_SPACE && type !== T_BREAK) lastSignificant = value
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
for (let i = 0; i < code.length;) {
|
|
49
|
+
const curr = code[i]
|
|
50
|
+
const next = code[i + 1]
|
|
51
|
+
|
|
52
|
+
const commentType = onCommentStart(curr, next, i, code)
|
|
53
|
+
if (commentType) {
|
|
54
|
+
const start = i++
|
|
55
|
+
while (i < code.length) {
|
|
56
|
+
if (onCommentEnd(code[i - 1], code[i], i, code) == commentType) {
|
|
57
|
+
i++
|
|
58
|
+
break
|
|
59
|
+
}
|
|
60
|
+
i++
|
|
61
|
+
}
|
|
62
|
+
append(T_COMMENT, code.slice(start, i))
|
|
63
|
+
continue
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const literalLength = options?.onLiteral?.(curr, i, code)
|
|
67
|
+
if (literalLength) {
|
|
68
|
+
append(T_STRING, code.slice(i, i + literalLength))
|
|
69
|
+
i += literalLength
|
|
70
|
+
continue
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (typeof options?.onQuote === 'function' && curr === "'") {
|
|
74
|
+
const length = options.onQuote(curr, i, code)
|
|
75
|
+
if (typeof length === 'number' && length >= 1) {
|
|
76
|
+
append(T_IDENTIFIER, code.slice(i, i + length))
|
|
77
|
+
i += length
|
|
78
|
+
continue
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (curr === '"' || curr === "'" || (options?.templateStrings && curr === '`')) {
|
|
83
|
+
const quote = curr
|
|
84
|
+
const start = i++
|
|
85
|
+
while (i < code.length) {
|
|
86
|
+
if (code[i] === quote && code[i - 1] !== '\\') {
|
|
87
|
+
i++
|
|
88
|
+
break
|
|
89
|
+
}
|
|
90
|
+
i++
|
|
91
|
+
}
|
|
92
|
+
const value = code.slice(start, i)
|
|
93
|
+
append(options?.quotedKeys && isQuotedKey(code, i) ? T_PROPERTY : T_STRING, value)
|
|
94
|
+
continue
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
if (curr === '\n') {
|
|
98
|
+
append(T_BREAK, curr)
|
|
99
|
+
i++
|
|
100
|
+
continue
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (/[^\S\r\n]/.test(curr)) {
|
|
104
|
+
const start = i++
|
|
105
|
+
while (i < code.length && /[^\S\r\n]/.test(code[i])) i++
|
|
106
|
+
append(T_SPACE, code.slice(start, i))
|
|
107
|
+
continue
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
if (isWord(curr)) {
|
|
111
|
+
const start = i++
|
|
112
|
+
while (i < code.length && isWord(code[i])) i++
|
|
113
|
+
const value = code.slice(start, i)
|
|
114
|
+
const normalized = normalize(value)
|
|
115
|
+
const type = typeKeywords.has(normalized)
|
|
116
|
+
? T_CLASS
|
|
117
|
+
: keywords.has(normalized)
|
|
118
|
+
? T_KEYWORD
|
|
119
|
+
: lastSignificant === '.'
|
|
120
|
+
? T_PROPERTY
|
|
121
|
+
: (/^\d/.test(value) || value === 'null' || /^\p{Lu}/u.test(value))
|
|
122
|
+
? T_CLASS
|
|
123
|
+
: T_IDENTIFIER
|
|
124
|
+
append(type, value)
|
|
125
|
+
continue
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
if (signs.has(curr)) {
|
|
129
|
+
append(T_SIGN, curr)
|
|
130
|
+
i++
|
|
131
|
+
continue
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
append(T_STRING, curr)
|
|
135
|
+
i++
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return tokens
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
/** @param {string} code @param {ParseOptions | undefined} options */
|
|
142
|
+
function parse(code, options) {
|
|
143
|
+
const parsed = assemble(code, tokenize(code, options))
|
|
144
|
+
if (options?.annotateLine) {
|
|
145
|
+
for (const line of parsed.lines) options.annotateLine(line)
|
|
146
|
+
}
|
|
147
|
+
return parsed
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/** @param {ParsedCode} parsed @param {DisplayOptions | undefined} options */
|
|
151
|
+
function render(parsed, options) {
|
|
152
|
+
return toHtml(generate(parsed, options))
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export { generate, parse, render, SugarHigh, tokenize }
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* @typedef {Object} ParseOptions
|
|
159
|
+
* @property {Set<string>} [keywords]
|
|
160
|
+
* @property {Set<string>} [typeKeywords]
|
|
161
|
+
* @property {(curr: string, next: string, index: number, code: string) => number | boolean} [onCommentStart]
|
|
162
|
+
* @property {(prev: string, curr: string, index: number, code: string) => number | boolean} [onCommentEnd]
|
|
163
|
+
* @property {(curr: string, index: number, code: string) => number | null | undefined} [onLiteral]
|
|
164
|
+
* @property {(curr: string, index: number, code: string) => number | null | undefined} [onQuote]
|
|
165
|
+
* @property {boolean} [quotedKeys]
|
|
166
|
+
* @property {boolean} [caseInsensitive]
|
|
167
|
+
* @property {boolean} [templateStrings]
|
|
168
|
+
* @property {(code: string, options: ParseOptions) => Array<[number, string]>} [tokenize]
|
|
169
|
+
* @property {(line: AnnotateLine) => void} [annotateLine]
|
|
170
|
+
*/
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* @typedef {Object} DisplayOptions
|
|
174
|
+
* @property {Partial<Record<TokenType, string>>} [cx]
|
|
175
|
+
* @property {(token: MarkToken) => void} [mark]
|
|
176
|
+
* @property {(line: MarkLine) => void} [markLine]
|
|
177
|
+
*/
|
|
178
|
+
|
|
179
|
+
/**
|
|
180
|
+
* @typedef {'identifier' | 'keyword' | 'string' | 'class' | 'property' | 'entity' | 'jsxliterals' | 'sign' | 'comment' | 'break' | 'space'} TokenType
|
|
181
|
+
* @typedef {{
|
|
182
|
+
* type: TokenType
|
|
183
|
+
* value: string
|
|
184
|
+
* className: string
|
|
185
|
+
* style: Record<string, string | number>
|
|
186
|
+
* properties: Record<string, string | number | boolean>
|
|
187
|
+
* }} MarkToken
|
|
188
|
+
* @typedef {{ type: TokenType, value: string }} ParsedToken
|
|
189
|
+
* @typedef {{
|
|
190
|
+
* index: number
|
|
191
|
+
* value: string
|
|
192
|
+
* tokens: ParsedToken[]
|
|
193
|
+
* annotations: string[]
|
|
194
|
+
* }} ParsedLine
|
|
195
|
+
* @typedef {ParsedLine} AnnotateLine
|
|
196
|
+
* @typedef {ParsedLine & {
|
|
197
|
+
* className: string
|
|
198
|
+
* style: Record<string, string | number>
|
|
199
|
+
* properties: Record<string, string | number | boolean>
|
|
200
|
+
* }} MarkLine
|
|
201
|
+
* @typedef {{ value: string, lines: ParsedLine[] }} ParsedCode
|
|
202
|
+
*/
|
package/lib/index.d.ts
CHANGED
|
@@ -1,25 +1,42 @@
|
|
|
1
|
-
type
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
1
|
+
import type {
|
|
2
|
+
DisplayOptions,
|
|
3
|
+
MarkLine,
|
|
4
|
+
MarkToken,
|
|
5
|
+
TokenType,
|
|
6
|
+
} from './core.js'
|
|
7
|
+
|
|
8
|
+
export type { DisplayOptions, MarkLine, MarkToken, TokenType }
|
|
9
|
+
|
|
10
|
+
export type LanguageName =
|
|
11
|
+
| 'javascript'
|
|
12
|
+
| 'typescript'
|
|
13
|
+
| 'css'
|
|
14
|
+
| 'python'
|
|
15
|
+
| 'c'
|
|
16
|
+
| 'go'
|
|
17
|
+
| 'java'
|
|
18
|
+
| 'rust'
|
|
19
|
+
| 'json'
|
|
20
|
+
| 'diff'
|
|
21
|
+
| 'shell'
|
|
22
|
+
| 'cpp'
|
|
23
|
+
| 'csharp'
|
|
24
|
+
| 'sql'
|
|
25
|
+
| 'html'
|
|
26
|
+
| 'yaml'
|
|
27
|
+
| 'markdown'
|
|
28
|
+
| 'kotlin'
|
|
29
|
+
| 'swift'
|
|
30
|
+
| 'php'
|
|
31
|
+
| 'toml'
|
|
32
|
+
| 'powershell'
|
|
33
|
+
| 'dockerfile'
|
|
34
|
+
| 'graphql'
|
|
35
|
+
| 'hcl'
|
|
36
|
+
|
|
37
|
+
export type HighlightOptions = DisplayOptions & {
|
|
38
|
+
/** Canonical language name. Fence and extension aliases must be normalized first. */
|
|
39
|
+
lang?: LanguageName
|
|
15
40
|
}
|
|
16
41
|
|
|
17
42
|
export function highlight(code: string, options?: HighlightOptions): string
|
|
18
|
-
export function tokenize(code: string, options?: HighlightOptions): Array<[number, string]>
|
|
19
|
-
export function generate(tokens: Array<[number, string]>, options?: Pick<HighlightOptions, 'lineClassName'>): Array<any>
|
|
20
|
-
export const SugarHigh: {
|
|
21
|
-
TokenTypes: {
|
|
22
|
-
[key: number]: string
|
|
23
|
-
}
|
|
24
|
-
TokenMap: Map<string, number>
|
|
25
|
-
}
|