micromark-extension-dl-list 0.1.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/LICENSE +21 -0
- package/README.md +79 -0
- package/package.json +54 -0
- package/src/constants.js +17 -0
- package/src/html.js +207 -0
- package/src/index.d.ts +9 -0
- package/src/index.js +2 -0
- package/src/lookahead.js +169 -0
- package/src/syntax.js +31 -0
- package/src/tokenize.js +462 -0
- package/src/util.js +72 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yohei Kanamura
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# micromark-extension-dl-list
|
|
2
|
+
|
|
3
|
+
A **micromark extension** that adds colon-based definition list syntax.
|
|
4
|
+
|
|
5
|
+
This package provides **syntax only** and is intended to be used with
|
|
6
|
+
remark or other unified pipelines.
|
|
7
|
+
|
|
8
|
+
For the detailed definition list syntax,
|
|
9
|
+
→ **[docs/syntax.md](https://github.com/kanemu/unified-dl-list/blob/main/docs/syntax.md)**.
|
|
10
|
+
|
|
11
|
+
## Installation
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
npm install micromark-extension-dl-list
|
|
15
|
+
````
|
|
16
|
+
|
|
17
|
+
or with pnpm:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pnpm add micromark-extension-dl-list
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
### With micromark (HTML output)
|
|
26
|
+
|
|
27
|
+
This package can be used directly with `micromark`
|
|
28
|
+
to parse colon-based definition lists and generate
|
|
29
|
+
`<dl>`, `<dt>`, and `<dd>` elements.
|
|
30
|
+
|
|
31
|
+
```js
|
|
32
|
+
import { micromark } from 'micromark'
|
|
33
|
+
import { dlList, dlListHtml } from 'micromark-extension-dl-list'
|
|
34
|
+
|
|
35
|
+
const md = `
|
|
36
|
+
: term
|
|
37
|
+
: description
|
|
38
|
+
: another description
|
|
39
|
+
`
|
|
40
|
+
|
|
41
|
+
const html = micromark(md, {
|
|
42
|
+
extensions: [dlList()],
|
|
43
|
+
htmlExtensions: [dlListHtml()]
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
console.log(html)
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Output:
|
|
50
|
+
|
|
51
|
+
```html
|
|
52
|
+
<dl>
|
|
53
|
+
<dt>term</dt>
|
|
54
|
+
<dd>description</dd>
|
|
55
|
+
<dd>another description</dd>
|
|
56
|
+
</dl>
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## What this package does
|
|
60
|
+
|
|
61
|
+
* Adds colon-based definition list syntax to micromark
|
|
62
|
+
* Emits tokens for `<dl>`, `<dt>`, and `<dd>`
|
|
63
|
+
|
|
64
|
+
## What this package does NOT do
|
|
65
|
+
|
|
66
|
+
- Does not generate mdast nodes
|
|
67
|
+
- Does not provide a remark plugin
|
|
68
|
+
|
|
69
|
+
## Related packages
|
|
70
|
+
|
|
71
|
+
This package is part of the **[unified-dl-list](https://github.com/kanemu/unified-dl-list)** monorepo:
|
|
72
|
+
|
|
73
|
+
- [`remark-dl-list`](https://github.com/kanemu/unified-dl-list/tree/main/packages/remark-dl-list)
|
|
74
|
+
- [`mdast-util-dl-list`](https://github.com/kanemu/unified-dl-list/tree/main/packages/mdast-util-dl-list)
|
|
75
|
+
- [`hast-util-dl-list`](https://github.com/kanemu/unified-dl-list/tree/main/packages/hast-util-dl-list)
|
|
76
|
+
|
|
77
|
+
## License
|
|
78
|
+
|
|
79
|
+
MIT
|
package/package.json
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "micromark-extension-dl-list",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "A micromark extension that adds colon-based definition list (<dl>, <dt>, <dd>) support with CommonMark-compatible parsing rules.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"sideEffects": false,
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/kanemu/unified-dl-list",
|
|
10
|
+
"directory": "packages/micromark-extension-dl-list"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/kanemu/unified-dl-list/tree/main/packages/micromark-extension-dl-list",
|
|
13
|
+
"bugs": {
|
|
14
|
+
"url": "https://github.com/kanemu/unified-dl-list/issues"
|
|
15
|
+
},
|
|
16
|
+
"exports": {
|
|
17
|
+
".": {
|
|
18
|
+
"types": "./src/index.d.ts",
|
|
19
|
+
"import": "./src/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./syntax.js": {
|
|
22
|
+
"import": "./src/syntax.js"
|
|
23
|
+
},
|
|
24
|
+
"./html.js": {
|
|
25
|
+
"import": "./src/html.js"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"files": [
|
|
29
|
+
"src/**/*.js",
|
|
30
|
+
"src/**/*.d.ts",
|
|
31
|
+
"README.md",
|
|
32
|
+
"LICENSE"
|
|
33
|
+
],
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"micromark": "^4.0.2",
|
|
36
|
+
"micromark-util-character": "^2.1.1",
|
|
37
|
+
"micromark-util-symbol": "^2.0.1"
|
|
38
|
+
},
|
|
39
|
+
"keywords": [
|
|
40
|
+
"micromark",
|
|
41
|
+
"micromark-extension",
|
|
42
|
+
"markdown",
|
|
43
|
+
"definition-list",
|
|
44
|
+
"dl",
|
|
45
|
+
"dt",
|
|
46
|
+
"dd",
|
|
47
|
+
"commonmark"
|
|
48
|
+
],
|
|
49
|
+
"author": "Yohei Kanamura",
|
|
50
|
+
"license": "MIT",
|
|
51
|
+
"scripts": {
|
|
52
|
+
"test": "node --test ./test/*.test.js"
|
|
53
|
+
}
|
|
54
|
+
}
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tab width used for column calculation.
|
|
3
|
+
* CommonMark / micromark treat a tab stop as 4 columns in indentation contexts.
|
|
4
|
+
*
|
|
5
|
+
* @type {number}
|
|
6
|
+
*/
|
|
7
|
+
export const TAB_SIZE = 4
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Upper bound (exclusive) of indentation columns allowed before `:` to start a dl-list construct.
|
|
11
|
+
*
|
|
12
|
+
* - CommonMark list rule allows up to 3 columns of indentation (0–3).
|
|
13
|
+
* - We keep it as an exclusive upper bound so callers can write `col < MAX_PREFIX_COLS`.
|
|
14
|
+
*
|
|
15
|
+
* @type {number}
|
|
16
|
+
*/
|
|
17
|
+
export const MAX_PREFIX_COLS = 4
|
package/src/html.js
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import { micromark } from 'micromark'
|
|
2
|
+
import { dlList } from './syntax.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @typedef {import('micromark-util-types').HtmlExtension} HtmlExtension
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {object} DlListHtmlOptions
|
|
10
|
+
* @property {number} [maxDepth] Maximum recursion depth for nested dl parsing inside dd containers.
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
function deindentByColumns(raw, cols) {
|
|
14
|
+
if (!cols) return raw
|
|
15
|
+
|
|
16
|
+
const text = raw.replace(/\r\n?/g, '\n')
|
|
17
|
+
const lines = text.split('\n')
|
|
18
|
+
|
|
19
|
+
return lines
|
|
20
|
+
.map((line) => {
|
|
21
|
+
let col = 0
|
|
22
|
+
let i = 0
|
|
23
|
+
|
|
24
|
+
while (i < line.length && col < cols) {
|
|
25
|
+
const ch = line.charCodeAt(i)
|
|
26
|
+
|
|
27
|
+
if (ch === 0x20) {
|
|
28
|
+
col += 1
|
|
29
|
+
i += 1
|
|
30
|
+
continue
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (ch === 0x09) {
|
|
34
|
+
const r = col % 4
|
|
35
|
+
const step = r === 0 ? 4 : 4 - r
|
|
36
|
+
if (col + step > cols) break
|
|
37
|
+
col += step
|
|
38
|
+
i += 1
|
|
39
|
+
continue
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
break
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
return line.slice(i)
|
|
46
|
+
})
|
|
47
|
+
.join('\n')
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isListMarkerLine(line) {
|
|
51
|
+
return /^([-*]|\d+\.)\s/.test(line)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function normalizeFlatListIndentInDd(raw) {
|
|
55
|
+
const text = raw.replace(/\r\n?/g, '\n')
|
|
56
|
+
const lines = text.split('\n')
|
|
57
|
+
|
|
58
|
+
let firstIdx = -1
|
|
59
|
+
for (let i = 0; i < lines.length; i++) {
|
|
60
|
+
if (lines[i].trim() !== '') {
|
|
61
|
+
firstIdx = i
|
|
62
|
+
break
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
if (firstIdx === -1) return raw
|
|
66
|
+
|
|
67
|
+
const first = lines[firstIdx]
|
|
68
|
+
if (!isListMarkerLine(first)) return raw
|
|
69
|
+
|
|
70
|
+
for (let i = firstIdx + 1; i < lines.length; i++) {
|
|
71
|
+
const line = lines[i]
|
|
72
|
+
if (line.startsWith(' ') && isListMarkerLine(line.slice(2))) {
|
|
73
|
+
lines[i] = line.slice(2)
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
return lines.join('\n')
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function normalizeNestedDlIndentInDd(raw) {
|
|
81
|
+
const text = raw.replace(/\r\n?/g, '\n')
|
|
82
|
+
const lines = text.split('\n')
|
|
83
|
+
|
|
84
|
+
let firstIdx = -1
|
|
85
|
+
for (let i = 0; i < lines.length; i++) {
|
|
86
|
+
if (lines[i].trim() !== '') {
|
|
87
|
+
firstIdx = i
|
|
88
|
+
break
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (firstIdx === -1) return raw
|
|
92
|
+
if (!lines[firstIdx].startsWith(':')) return raw
|
|
93
|
+
|
|
94
|
+
for (let i = firstIdx + 1; i < lines.length; i++) {
|
|
95
|
+
const line = lines[i]
|
|
96
|
+
if (!line.startsWith(' ')) continue
|
|
97
|
+
|
|
98
|
+
let n = 0
|
|
99
|
+
while (n < line.length && line.charCodeAt(n) === 0x20) n++
|
|
100
|
+
|
|
101
|
+
if (n >= 2 && line.charCodeAt(n) === 0x3a && (n - 2) % 4 === 0) {
|
|
102
|
+
lines[i] = line.slice(2)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
return lines.join('\n')
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function renderInlineMarkdown(raw) {
|
|
110
|
+
let html = micromark(raw)
|
|
111
|
+
const m = html.match(/^<p>([\s\S]*)<\/p>\n?$/)
|
|
112
|
+
if (m) return m[1]
|
|
113
|
+
return html
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* HTML extension for dl-list tokens.
|
|
118
|
+
*
|
|
119
|
+
* - `dlTermText` is rendered as inline markdown.
|
|
120
|
+
* - `dlDescContainer` is deindented and re-parsed as block markdown (with nested dl support).
|
|
121
|
+
*
|
|
122
|
+
* @param {DlListHtmlOptions=} options
|
|
123
|
+
* @returns {HtmlExtension}
|
|
124
|
+
*/
|
|
125
|
+
export function dlListHtml(options = {}) {
|
|
126
|
+
const maxDepth = options.maxDepth ?? 8
|
|
127
|
+
|
|
128
|
+
/** @type {HtmlExtension} */
|
|
129
|
+
return {
|
|
130
|
+
enter: {
|
|
131
|
+
dlList() {
|
|
132
|
+
this.tag('<dl>')
|
|
133
|
+
},
|
|
134
|
+
|
|
135
|
+
dlItem() { },
|
|
136
|
+
|
|
137
|
+
dlTerm() {
|
|
138
|
+
this.tag('<dt>')
|
|
139
|
+
},
|
|
140
|
+
dlDesc() {
|
|
141
|
+
this.tag('<dd>')
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
dlIndent() { },
|
|
145
|
+
dlMarkerSpace() { },
|
|
146
|
+
dlLineEnding() { },
|
|
147
|
+
|
|
148
|
+
dlHardBreak() {
|
|
149
|
+
this.raw('\n')
|
|
150
|
+
},
|
|
151
|
+
|
|
152
|
+
dlTermText() { },
|
|
153
|
+
|
|
154
|
+
dlDescContainer() { }
|
|
155
|
+
},
|
|
156
|
+
|
|
157
|
+
exit: {
|
|
158
|
+
dlList() {
|
|
159
|
+
this.tag('</dl>')
|
|
160
|
+
},
|
|
161
|
+
|
|
162
|
+
dlItem() { },
|
|
163
|
+
|
|
164
|
+
dlTerm() {
|
|
165
|
+
this.tag('</dt>')
|
|
166
|
+
},
|
|
167
|
+
dlDesc() {
|
|
168
|
+
this.tag('</dd>')
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
dlTermText(token) {
|
|
172
|
+
const raw = this.sliceSerialize(token)
|
|
173
|
+
this.raw(renderInlineMarkdown(raw))
|
|
174
|
+
},
|
|
175
|
+
|
|
176
|
+
dlIndent() { },
|
|
177
|
+
dlMarkerSpace() { },
|
|
178
|
+
dlLineEnding() { },
|
|
179
|
+
|
|
180
|
+
dlDescContainer(token) {
|
|
181
|
+
let raw = this.sliceSerialize(token)
|
|
182
|
+
raw = deindentByColumns(raw, token._dlIndent || 0)
|
|
183
|
+
raw = normalizeFlatListIndentInDd(raw)
|
|
184
|
+
raw = normalizeNestedDlIndentInDd(raw)
|
|
185
|
+
|
|
186
|
+
if (maxDepth <= 0) {
|
|
187
|
+
this.raw(micromark(raw))
|
|
188
|
+
return
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const html = micromark(raw, {
|
|
192
|
+
extensions: [dlList()],
|
|
193
|
+
htmlExtensions: [dlListHtml({ maxDepth: maxDepth - 1 })]
|
|
194
|
+
})
|
|
195
|
+
|
|
196
|
+
const trimmed = html.replace(/^\s+|\s+$/g, '')
|
|
197
|
+
const m = trimmed.match(/^<p>([\s\S]*)<\/p>$/)
|
|
198
|
+
if (m) {
|
|
199
|
+
this.raw(m[1])
|
|
200
|
+
return
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
this.raw(html)
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
package/src/index.d.ts
ADDED
package/src/index.js
ADDED
package/src/lookahead.js
ADDED
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { markdownLineEnding } from 'micromark-util-character'
|
|
2
|
+
import { codes } from 'micromark-util-symbol'
|
|
3
|
+
import { MAX_PREFIX_COLS } from './constants.js'
|
|
4
|
+
import { isEof, isIndent, consumeSafe, advanceColumn } from './util.js'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Lookahead tokenizers used by dl-list to confirm start / continuation without consuming input.
|
|
8
|
+
*
|
|
9
|
+
* These are separated from tokenize.js to keep the main tokenizer readable.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Check whether the current line can start dl-list after optional indentation (<= 3 cols).
|
|
14
|
+
* Succeeds when it can reach ':' before exceeding MAX_PREFIX_COLS.
|
|
15
|
+
*/
|
|
16
|
+
export function checkPrefixFactory() {
|
|
17
|
+
return {
|
|
18
|
+
tokenize(effects2, ok2, nok2) {
|
|
19
|
+
let col = 0
|
|
20
|
+
return start2
|
|
21
|
+
|
|
22
|
+
/** @type {import('micromark-util-types').State} */
|
|
23
|
+
function start2(code) {
|
|
24
|
+
if (isEof(code)) return nok2(code)
|
|
25
|
+
if (code === codes.colon) return ok2(code)
|
|
26
|
+
|
|
27
|
+
if (isIndent(code) && col < MAX_PREFIX_COLS) {
|
|
28
|
+
col = advanceColumn(col, code)
|
|
29
|
+
consumeSafe(effects2, code)
|
|
30
|
+
if (col > MAX_PREFIX_COLS) return nok2(code)
|
|
31
|
+
return start2
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return nok2(code)
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Check whether the dl-list should continue after a line ending.
|
|
42
|
+
*
|
|
43
|
+
* - ok when next line begins with ':' or indentation
|
|
44
|
+
* - stop on blank line (do not consume blank-line EOL in the main tokenizer)
|
|
45
|
+
*/
|
|
46
|
+
export function checkAfterEolContinueFactory() {
|
|
47
|
+
return {
|
|
48
|
+
tokenize(effects2, ok2, nok2) {
|
|
49
|
+
let opened = false
|
|
50
|
+
return start2
|
|
51
|
+
|
|
52
|
+
function start2(code) {
|
|
53
|
+
if (isEof(code)) return ok2(code)
|
|
54
|
+
if (!markdownLineEnding(code)) return nok2(code)
|
|
55
|
+
|
|
56
|
+
effects2.enter('dlCheck')
|
|
57
|
+
opened = true
|
|
58
|
+
|
|
59
|
+
// consume the line ending so we can inspect the next line head
|
|
60
|
+
effects2.consume(code)
|
|
61
|
+
return head
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function head(code) {
|
|
65
|
+
if (isEof(code)) return endOk(code)
|
|
66
|
+
if (markdownLineEnding(code)) return endNok(code) // blank line -> stop
|
|
67
|
+
if (code === codes.colon) return endOk(code)
|
|
68
|
+
if (isIndent(code)) return endOk(code)
|
|
69
|
+
return endNok(code)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
function endOk(code) {
|
|
73
|
+
if (opened) {
|
|
74
|
+
effects2.exit('dlCheck')
|
|
75
|
+
opened = false
|
|
76
|
+
}
|
|
77
|
+
return ok2(code)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function endNok(code) {
|
|
81
|
+
if (opened) {
|
|
82
|
+
effects2.exit('dlCheck')
|
|
83
|
+
opened = false
|
|
84
|
+
}
|
|
85
|
+
return nok2(code)
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Check whether a ':' at baseIndent can start a dl-list.
|
|
93
|
+
*
|
|
94
|
+
* Requires:
|
|
95
|
+
* - the ':' line itself exists
|
|
96
|
+
* - next line is EOF or blank, OR
|
|
97
|
+
* - next line begins with ':' at baseIndent or ddIndent, OR
|
|
98
|
+
* - next line is indented beyond baseIndent (continuation for dt)
|
|
99
|
+
*/
|
|
100
|
+
export function checkDlStartFactory(baseIndentArg, ddIndentArg) {
|
|
101
|
+
return {
|
|
102
|
+
tokenize(effects2, ok2, nok2) {
|
|
103
|
+
let col = 0
|
|
104
|
+
let opened = false
|
|
105
|
+
return start2
|
|
106
|
+
|
|
107
|
+
function start2(code) {
|
|
108
|
+
if (isEof(code)) return nok2(code)
|
|
109
|
+
if (code !== codes.colon) return nok2(code)
|
|
110
|
+
|
|
111
|
+
effects2.enter('dlCheck')
|
|
112
|
+
opened = true
|
|
113
|
+
|
|
114
|
+
effects2.consume(code) // ':'
|
|
115
|
+
return restOfLine
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
function restOfLine(code) {
|
|
119
|
+
if (isEof(code)) return endOk(code)
|
|
120
|
+
|
|
121
|
+
if (markdownLineEnding(code)) {
|
|
122
|
+
effects2.consume(code) // consume EOL to inspect next line head
|
|
123
|
+
col = 0
|
|
124
|
+
return nextLineHead
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
effects2.consume(code)
|
|
128
|
+
return restOfLine
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function nextLineHead(code) {
|
|
132
|
+
if (isEof(code)) return endOk(code) // allow EOF
|
|
133
|
+
if (markdownLineEnding(code)) return endOk(code) // allow blank line
|
|
134
|
+
|
|
135
|
+
if (isIndent(code) && col < 512) {
|
|
136
|
+
col = advanceColumn(col, code)
|
|
137
|
+
effects2.consume(code)
|
|
138
|
+
return nextLineHead
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// next line must start a field: ":" at baseIndent or ddIndent
|
|
142
|
+
if (code === codes.colon && (col === baseIndentArg || col === ddIndentArg)) {
|
|
143
|
+
return endOk(code)
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// allow an indented, non-blank continuation line for the term
|
|
147
|
+
if (col > baseIndentArg) return endOk(code)
|
|
148
|
+
|
|
149
|
+
return endNok(code)
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function endOk(code) {
|
|
153
|
+
if (opened) {
|
|
154
|
+
effects2.exit('dlCheck')
|
|
155
|
+
opened = false
|
|
156
|
+
}
|
|
157
|
+
return ok2(code)
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function endNok(code) {
|
|
161
|
+
if (opened) {
|
|
162
|
+
effects2.exit('dlCheck')
|
|
163
|
+
opened = false
|
|
164
|
+
}
|
|
165
|
+
return nok2(code)
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
package/src/syntax.js
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { codes } from 'micromark-util-symbol'
|
|
2
|
+
import { tokenizeDlList } from './tokenize.js'
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Micromark extension for colon-based definition lists.
|
|
6
|
+
*
|
|
7
|
+
* Syntax (flow):
|
|
8
|
+
* - A line whose first non-indentation character within 0–3 columns is `:` starts a dl-list.
|
|
9
|
+
* - The first `:` line is a term (`dt`).
|
|
10
|
+
* - Subsequent lines indented by 4+ columns and starting with `:` are descriptions (`dd`).
|
|
11
|
+
* - Continuation lines (indented, without `:`) are appended to the last opened dt/dd.
|
|
12
|
+
*
|
|
13
|
+
* Design constraints:
|
|
14
|
+
* - Do not consume indentation unless dl-list is confirmed by lookahead.
|
|
15
|
+
* - Do not consume blank-line EOL that terminates the list.
|
|
16
|
+
*
|
|
17
|
+
* @returns {import('micromark-util-types').Extension}
|
|
18
|
+
*/
|
|
19
|
+
export function dlList() {
|
|
20
|
+
/** @type {import('micromark-util-types').Construct} */
|
|
21
|
+
const construct = { name: 'dlList', tokenize: tokenizeDlList, concrete: true }
|
|
22
|
+
|
|
23
|
+
/** @type {import('micromark-util-types').Extension} */
|
|
24
|
+
return {
|
|
25
|
+
flow: {
|
|
26
|
+
[codes.colon]: construct,
|
|
27
|
+
[codes.space]: construct,
|
|
28
|
+
[codes.ht]: construct
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
package/src/tokenize.js
ADDED
|
@@ -0,0 +1,462 @@
|
|
|
1
|
+
import { markdownLineEnding } from 'micromark-util-character'
|
|
2
|
+
import { codes } from 'micromark-util-symbol'
|
|
3
|
+
import { MAX_PREFIX_COLS } from './constants.js'
|
|
4
|
+
import {
|
|
5
|
+
isEof,
|
|
6
|
+
isIndent,
|
|
7
|
+
consumeSafe,
|
|
8
|
+
advanceColumn,
|
|
9
|
+
consumeLineEndingSafe
|
|
10
|
+
} from './util.js'
|
|
11
|
+
import {
|
|
12
|
+
checkPrefixFactory,
|
|
13
|
+
checkAfterEolContinueFactory,
|
|
14
|
+
checkDlStartFactory
|
|
15
|
+
} from './lookahead.js'
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Tokenize a dl-list at flow level.
|
|
19
|
+
*
|
|
20
|
+
* @internal
|
|
21
|
+
* @this {import('micromark-util-types').TokenizeContext}
|
|
22
|
+
* @param {import('micromark-util-types').Effects} effects
|
|
23
|
+
* @param {import('micromark-util-types').State} ok
|
|
24
|
+
* @param {import('micromark-util-types').State} nok
|
|
25
|
+
* @returns {import('micromark-util-types').State}
|
|
26
|
+
*/
|
|
27
|
+
export function tokenizeDlList(effects, ok, nok) {
|
|
28
|
+
/** @type {number} */
|
|
29
|
+
let baseIndent = 0
|
|
30
|
+
|
|
31
|
+
/** @type {number} */
|
|
32
|
+
let ddIndent = 4
|
|
33
|
+
|
|
34
|
+
/** @type {'term'|'desc'|null} */
|
|
35
|
+
let lastField = null
|
|
36
|
+
|
|
37
|
+
/** @type {boolean} */
|
|
38
|
+
let listOpen = false
|
|
39
|
+
/** @type {boolean} */
|
|
40
|
+
let itemOpen = false
|
|
41
|
+
/** @type {boolean} */
|
|
42
|
+
let termOpen = false
|
|
43
|
+
/** @type {boolean} */
|
|
44
|
+
let descOpen = false
|
|
45
|
+
/** @type {boolean} */
|
|
46
|
+
let termTextOpen = false
|
|
47
|
+
|
|
48
|
+
const closeTermTextIfOpen = () => {
|
|
49
|
+
if (termTextOpen) {
|
|
50
|
+
effects.exit('dlTermText')
|
|
51
|
+
termTextOpen = false
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const closeTermIfOpen = () => {
|
|
56
|
+
closeTermTextIfOpen()
|
|
57
|
+
if (termOpen) {
|
|
58
|
+
effects.exit('dlTerm')
|
|
59
|
+
termOpen = false
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const closeDescIfOpen = () => {
|
|
64
|
+
if (descOpen) {
|
|
65
|
+
effects.exit('dlDesc')
|
|
66
|
+
descOpen = false
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const closeFieldIfOpen = () => {
|
|
71
|
+
closeDescIfOpen()
|
|
72
|
+
closeTermIfOpen()
|
|
73
|
+
lastField = null
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
const closeItemIfOpen = () => {
|
|
77
|
+
closeFieldIfOpen()
|
|
78
|
+
if (itemOpen) {
|
|
79
|
+
effects.exit('dlItem')
|
|
80
|
+
itemOpen = false
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const closeAll = () => {
|
|
85
|
+
closeItemIfOpen()
|
|
86
|
+
if (listOpen) {
|
|
87
|
+
effects.exit('dlList')
|
|
88
|
+
listOpen = false
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
const start = (code) => {
|
|
93
|
+
baseIndent = 0
|
|
94
|
+
ddIndent = 4
|
|
95
|
+
lastField = null
|
|
96
|
+
listOpen = false
|
|
97
|
+
itemOpen = false
|
|
98
|
+
termOpen = false
|
|
99
|
+
descOpen = false
|
|
100
|
+
termTextOpen = false
|
|
101
|
+
return prefix(code, 0)
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const prefix = (code, col) => {
|
|
105
|
+
if (isEof(code)) return nok(code)
|
|
106
|
+
|
|
107
|
+
if (code === codes.colon) {
|
|
108
|
+
if (col > 3) return nok(code)
|
|
109
|
+
|
|
110
|
+
return effects.check(
|
|
111
|
+
checkDlStartFactory(col, col + 4),
|
|
112
|
+
onOk,
|
|
113
|
+
nok
|
|
114
|
+
)(code)
|
|
115
|
+
|
|
116
|
+
function onOk() {
|
|
117
|
+
baseIndent = col
|
|
118
|
+
ddIndent = baseIndent + 4
|
|
119
|
+
effects.enter('dlList')
|
|
120
|
+
listOpen = true
|
|
121
|
+
return termMarker(code)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (isIndent(code) && col < MAX_PREFIX_COLS) {
|
|
126
|
+
return effects.check(checkPrefixFactory(), onOk, nok)(code)
|
|
127
|
+
function onOk() {
|
|
128
|
+
return prefixConsume(code, 0)
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
return nok(code)
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
const prefixConsume = (code, col) => {
|
|
136
|
+
if (isEof(code)) return nok(code)
|
|
137
|
+
|
|
138
|
+
if (code === codes.colon) {
|
|
139
|
+
if (col > 3) return nok(code)
|
|
140
|
+
|
|
141
|
+
return effects.check(
|
|
142
|
+
checkDlStartFactory(col, col + 4),
|
|
143
|
+
onOk,
|
|
144
|
+
nok
|
|
145
|
+
)(code)
|
|
146
|
+
|
|
147
|
+
function onOk() {
|
|
148
|
+
baseIndent = col
|
|
149
|
+
ddIndent = baseIndent + 4
|
|
150
|
+
effects.enter('dlList')
|
|
151
|
+
listOpen = true
|
|
152
|
+
return termMarker(code)
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
if (isIndent(code) && col < MAX_PREFIX_COLS) {
|
|
157
|
+
effects.enter('dlIndent')
|
|
158
|
+
consumeSafe(effects, code)
|
|
159
|
+
effects.exit('dlIndent')
|
|
160
|
+
|
|
161
|
+
const nextCol = advanceColumn(col, code)
|
|
162
|
+
if (nextCol > MAX_PREFIX_COLS) return nok(code)
|
|
163
|
+
|
|
164
|
+
return (c) => prefixConsume(c, nextCol)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return nok(code)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const termMarker = (code) => {
|
|
171
|
+
if (isEof(code) || code !== codes.colon) return nok(code)
|
|
172
|
+
|
|
173
|
+
closeItemIfOpen()
|
|
174
|
+
|
|
175
|
+
effects.enter('dlItem')
|
|
176
|
+
itemOpen = true
|
|
177
|
+
|
|
178
|
+
effects.enter('dlTerm')
|
|
179
|
+
termOpen = true
|
|
180
|
+
lastField = 'term'
|
|
181
|
+
|
|
182
|
+
consumeSafe(effects, code) // ':'
|
|
183
|
+
return afterMarkerToTerm
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
const afterMarkerToTerm = (code) => {
|
|
187
|
+
if (isEof(code) || markdownLineEnding(code)) return afterEol(code)
|
|
188
|
+
|
|
189
|
+
if (isIndent(code)) {
|
|
190
|
+
effects.enter('dlMarkerSpace')
|
|
191
|
+
consumeSafe(effects, code)
|
|
192
|
+
effects.exit('dlMarkerSpace')
|
|
193
|
+
return termTextStart
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return termTextStart(code)
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const termTextStart = (code) => {
|
|
200
|
+
effects.enter('dlTermText')
|
|
201
|
+
termTextOpen = true
|
|
202
|
+
return termText(code)
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
const termText = (code) => {
|
|
206
|
+
if (isEof(code) || markdownLineEnding(code)) {
|
|
207
|
+
effects.exit('dlTermText')
|
|
208
|
+
termTextOpen = false
|
|
209
|
+
return afterEol(code)
|
|
210
|
+
}
|
|
211
|
+
consumeSafe(effects, code)
|
|
212
|
+
return termText
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
const afterEol = (code) => {
|
|
216
|
+
if (isEof(code)) {
|
|
217
|
+
closeAll()
|
|
218
|
+
return ok(code)
|
|
219
|
+
}
|
|
220
|
+
if (!markdownLineEnding(code)) return nok(code)
|
|
221
|
+
|
|
222
|
+
// Decide whether to continue *before* claiming the line ending.
|
|
223
|
+
// If we stop here, leave the line ending to the parent tokenizer.
|
|
224
|
+
return effects.check(
|
|
225
|
+
checkAfterEolContinueFactory(),
|
|
226
|
+
onContinue,
|
|
227
|
+
onStop
|
|
228
|
+
)(code)
|
|
229
|
+
|
|
230
|
+
function onContinue() {
|
|
231
|
+
consumeLineEndingSafe(effects, code)
|
|
232
|
+
return lineStart
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function onStop() {
|
|
236
|
+
closeAll()
|
|
237
|
+
return ok(code)
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const lineStart = (code) => {
|
|
242
|
+
if (isEof(code) || markdownLineEnding(code)) {
|
|
243
|
+
closeAll()
|
|
244
|
+
return ok(code)
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
if (code === codes.colon) {
|
|
248
|
+
closeFieldIfOpen()
|
|
249
|
+
return termMarker(code)
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
if (isIndent(code)) return scanIndent(code, 0)
|
|
253
|
+
|
|
254
|
+
closeAll()
|
|
255
|
+
return ok(code)
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
const scanIndent = (code, col) => {
|
|
259
|
+
if (isEof(code)) {
|
|
260
|
+
closeAll()
|
|
261
|
+
return ok(code)
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
if (isIndent(code) && col < 512) {
|
|
265
|
+
effects.enter('dlIndent')
|
|
266
|
+
consumeSafe(effects, code)
|
|
267
|
+
effects.exit('dlIndent')
|
|
268
|
+
|
|
269
|
+
const nextCol = advanceColumn(col, code)
|
|
270
|
+
return (c) => scanIndent(c, nextCol)
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
if (col >= ddIndent && code === codes.colon) {
|
|
274
|
+
closeTermIfOpen()
|
|
275
|
+
return descMarker(code)
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
if (col === baseIndent && code === codes.colon) {
|
|
279
|
+
closeFieldIfOpen()
|
|
280
|
+
return termMarker(code)
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
if (col > baseIndent) {
|
|
284
|
+
if (!termOpen && !descOpen) {
|
|
285
|
+
closeAll()
|
|
286
|
+
return ok(code)
|
|
287
|
+
}
|
|
288
|
+
return continuationLine(code)
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
closeAll()
|
|
292
|
+
return ok(code)
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
const descMarker = (code) => {
|
|
296
|
+
if (isEof(code) || code !== codes.colon) return nok(code)
|
|
297
|
+
|
|
298
|
+
closeDescIfOpen()
|
|
299
|
+
|
|
300
|
+
effects.enter('dlDesc')
|
|
301
|
+
descOpen = true
|
|
302
|
+
lastField = 'desc'
|
|
303
|
+
|
|
304
|
+
consumeSafe(effects, code) // ':'
|
|
305
|
+
return afterMarkerToDesc
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const afterMarkerToDesc = (code) => {
|
|
309
|
+
if (isEof(code)) return afterEol(code)
|
|
310
|
+
|
|
311
|
+
// IMPORTANT:
|
|
312
|
+
// Even if the dd marker line ends immediately, open a container so
|
|
313
|
+
// subsequent indented lines become part of this dd.
|
|
314
|
+
// (This removes the need for dlDescText.)
|
|
315
|
+
if (markdownLineEnding(code)) {
|
|
316
|
+
const t = effects.enter('dlDescContainer')
|
|
317
|
+
// @ts-ignore
|
|
318
|
+
t._dlIndent = ddIndent
|
|
319
|
+
return descContainerContent(code)
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
// dd マーカー直後のスペースはコンテナに入れない(見た目調整用)
|
|
323
|
+
if (isIndent(code)) {
|
|
324
|
+
effects.enter('dlMarkerSpace')
|
|
325
|
+
consumeSafe(effects, code)
|
|
326
|
+
effects.exit('dlMarkerSpace')
|
|
327
|
+
return afterMarkerToDesc
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const t = effects.enter('dlDescContainer')
|
|
331
|
+
// html.js が参照する deindent 量(columns)
|
|
332
|
+
// @ts-ignore
|
|
333
|
+
t._dlIndent = ddIndent
|
|
334
|
+
|
|
335
|
+
return descContainerContent(code)
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const descContainerContent = (code) => {
|
|
339
|
+
if (isEof(code)) {
|
|
340
|
+
effects.exit('dlDescContainer')
|
|
341
|
+
closeAll()
|
|
342
|
+
return ok(code)
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
if (markdownLineEnding(code)) {
|
|
346
|
+
return effects.check(
|
|
347
|
+
checkAfterEolContinueFactory(),
|
|
348
|
+
onContinue,
|
|
349
|
+
onStop
|
|
350
|
+
)(code)
|
|
351
|
+
|
|
352
|
+
function onContinue() {
|
|
353
|
+
consumeLineEndingSafe(effects, code)
|
|
354
|
+
return descContainerLineStart
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
function onStop() {
|
|
358
|
+
effects.exit('dlDescContainer')
|
|
359
|
+
closeAll()
|
|
360
|
+
return ok(code)
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
consumeSafe(effects, code)
|
|
365
|
+
return descContainerContent
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
const descContainerLineStart = (code) => {
|
|
369
|
+
if (isEof(code)) {
|
|
370
|
+
effects.exit('dlDescContainer')
|
|
371
|
+
closeAll()
|
|
372
|
+
return ok(code)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (markdownLineEnding(code)) {
|
|
376
|
+
// 空行はコンテナに含める(段落分離に必要)
|
|
377
|
+
consumeLineEndingSafe(effects, code)
|
|
378
|
+
return descContainerLineStart
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// 次行が ":" で始まる (= 次の term / 同階層) 場合、dd コンテナを閉じて tokenizer 側で処理
|
|
382
|
+
if (code === codes.colon) {
|
|
383
|
+
effects.exit('dlDescContainer')
|
|
384
|
+
closeDescIfOpen()
|
|
385
|
+
return lineStart(code)
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
if (isIndent(code)) return descContainerScanIndent(code, 0)
|
|
389
|
+
|
|
390
|
+
// インデント無しは dl-list 終了
|
|
391
|
+
effects.exit('dlDescContainer')
|
|
392
|
+
closeAll()
|
|
393
|
+
return ok(code)
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
const descContainerScanIndent = (code, col) => {
|
|
397
|
+
if (isEof(code)) {
|
|
398
|
+
effects.exit('dlDescContainer')
|
|
399
|
+
closeAll()
|
|
400
|
+
return ok(code)
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (isIndent(code) && col < 512) {
|
|
404
|
+
// コンテナなので indent もそのまま入れる
|
|
405
|
+
consumeSafe(effects, code)
|
|
406
|
+
const nextCol = advanceColumn(col, code)
|
|
407
|
+
return (c) => descContainerScanIndent(c, nextCol)
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (col === baseIndent && code === codes.colon) {
|
|
411
|
+
effects.exit('dlDescContainer')
|
|
412
|
+
closeDescIfOpen()
|
|
413
|
+
return termMarker(code)
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
// 深いインデントの ":" は dd 本文(入れ子 dl 等)の可能性があるので閉じない
|
|
417
|
+
if (col === ddIndent && code === codes.colon) {
|
|
418
|
+
effects.exit('dlDescContainer')
|
|
419
|
+
closeDescIfOpen()
|
|
420
|
+
return descMarker(code)
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
// それ以外は dd 本文継続(この行の残りを食う)
|
|
424
|
+
return descContainerContent(code)
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
const continuationLine = (code) => {
|
|
428
|
+
effects.enter('dlHardBreak')
|
|
429
|
+
effects.exit('dlHardBreak')
|
|
430
|
+
|
|
431
|
+
if (lastField === 'term' && termOpen) {
|
|
432
|
+
effects.enter('dlTermText')
|
|
433
|
+
termTextOpen = true
|
|
434
|
+
return contTextAsTerm(code)
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
if (lastField === 'desc' && descOpen) {
|
|
438
|
+
// Fallback safety:
|
|
439
|
+
// If we ever reach here with an open dd but no container,
|
|
440
|
+
// treat continuation as dd container content (no dlDescText).
|
|
441
|
+
const t = effects.enter('dlDescContainer')
|
|
442
|
+
// @ts-ignore
|
|
443
|
+
t._dlIndent = ddIndent
|
|
444
|
+
return descContainerContent(code)
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
closeAll()
|
|
448
|
+
return ok(code)
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const contTextAsTerm = (code) => {
|
|
452
|
+
if (isEof(code) || markdownLineEnding(code)) {
|
|
453
|
+
effects.exit('dlTermText')
|
|
454
|
+
termTextOpen = false
|
|
455
|
+
return afterEol(code)
|
|
456
|
+
}
|
|
457
|
+
consumeSafe(effects, code)
|
|
458
|
+
return contTextAsTerm
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
return start
|
|
462
|
+
}
|
package/src/util.js
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { markdownLineEnding } from 'micromark-util-character'
|
|
2
|
+
import { codes } from 'micromark-util-symbol'
|
|
3
|
+
import { TAB_SIZE } from './constants.js'
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @param {number|null} code
|
|
7
|
+
* @returns {boolean}
|
|
8
|
+
*/
|
|
9
|
+
export function isEof(code) {
|
|
10
|
+
return code === codes.eof
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* True for indentation codes that micromark uses at line starts.
|
|
15
|
+
*
|
|
16
|
+
* @param {number|null} code
|
|
17
|
+
* @returns {boolean}
|
|
18
|
+
*/
|
|
19
|
+
export function isIndent(code) {
|
|
20
|
+
return (
|
|
21
|
+
code === codes.space ||
|
|
22
|
+
code === codes.ht ||
|
|
23
|
+
code === codes.virtualSpace ||
|
|
24
|
+
code === codes.horizontalTab
|
|
25
|
+
)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Consume a code only when it represents a number.
|
|
30
|
+
* (micromark uses negative “virtual” codes too; those are still numbers and are valid to consume.)
|
|
31
|
+
*
|
|
32
|
+
* @param {import('micromark-util-types').Effects} effects
|
|
33
|
+
* @param {number|null} code
|
|
34
|
+
* @returns {void}
|
|
35
|
+
*/
|
|
36
|
+
export function consumeSafe(effects, code) {
|
|
37
|
+
if (typeof code !== 'number') return
|
|
38
|
+
effects.consume(code)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Advance column count by one character, respecting TAB_SIZE.
|
|
43
|
+
*
|
|
44
|
+
* @param {number} col
|
|
45
|
+
* @param {number|null} code
|
|
46
|
+
* @returns {number}
|
|
47
|
+
*/
|
|
48
|
+
export function advanceColumn(col, code) {
|
|
49
|
+
if (code === codes.ht || code === codes.horizontalTab) {
|
|
50
|
+
const r = col % TAB_SIZE
|
|
51
|
+
return col + (r === 0 ? TAB_SIZE : TAB_SIZE - r)
|
|
52
|
+
}
|
|
53
|
+
return col + 1
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Consume a line ending as a token (`dlLineEnding`) so downstream can reason about it.
|
|
58
|
+
*
|
|
59
|
+
* NOTE:
|
|
60
|
+
* - Do not call this in the "blank line ends dl-list" path.
|
|
61
|
+
* In that case, the EOL must remain for CommonMark to see the blank-line boundary.
|
|
62
|
+
*
|
|
63
|
+
* @param {import('micromark-util-types').Effects} effects
|
|
64
|
+
* @param {number|null} code
|
|
65
|
+
* @returns {void}
|
|
66
|
+
*/
|
|
67
|
+
export function consumeLineEndingSafe(effects, code) {
|
|
68
|
+
if (!markdownLineEnding(code)) return
|
|
69
|
+
effects.enter('dlLineEnding')
|
|
70
|
+
effects.consume(code)
|
|
71
|
+
effects.exit('dlLineEnding')
|
|
72
|
+
}
|