@peaceroad/markdown-it-numbering-ul-regarded-as-ol 0.2.3 → 0.4.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 +20 -5
- package/index.js +38 -54
- package/package.json +2 -2
- package/src/phase0-description-list.js +232 -113
- package/src/phase1-analyze.js +39 -8
- package/src/phase2-convert.js +177 -193
- package/src/phase3-attributes.js +24 -47
- package/src/phase5-spans.js +73 -54
- package/src/preprocess-literal-lists.js +295 -362
- package/src/types-utility.js +0 -15
- package/src/phase6-attrs-migration.js +0 -184
|
@@ -3,15 +3,145 @@
|
|
|
3
3
|
import { detectMarkerType } from './types-utility.js'
|
|
4
4
|
import { findMatchingClose, findListItemEnd } from './list-helpers.js'
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
const
|
|
6
|
+
// markdown-it treats indent >= marker width + 4 as code blocks inside list items.
|
|
7
|
+
const MAX_LITERAL_INLINE_INDENT = 3
|
|
8
|
+
const LITERAL_SUFFIX_CHARS = new Set(['.', ')', '.', ')', '、'])
|
|
9
|
+
const LITERAL_OPEN_CLOSE_PAIRS = new Map([
|
|
10
|
+
['(', ')'],
|
|
11
|
+
['(', ')']
|
|
12
|
+
])
|
|
13
|
+
const ASCII_ALNUM_OR_FULLWIDTH_DIGIT_REGEX = /^[A-Za-z0-90-9]+$/
|
|
14
|
+
const getIndentWidth = (indentText) => indentText.replace(/\t/g, ' ').length
|
|
15
|
+
const buildLineMap = (startLine, endLine = null) => {
|
|
16
|
+
if (typeof startLine !== 'number') {
|
|
17
|
+
return null
|
|
18
|
+
}
|
|
19
|
+
const normalizedEnd = typeof endLine === 'number' ? endLine + 1 : startLine + 1
|
|
20
|
+
return [startLine, normalizedEnd]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const getListItemMarkerWidth = (listItem) => {
|
|
24
|
+
if (!listItem) {
|
|
25
|
+
return 1
|
|
26
|
+
}
|
|
27
|
+
const info = typeof listItem.info === 'string' ? listItem.info : ''
|
|
28
|
+
const markup = typeof listItem.markup === 'string' ? listItem.markup : ''
|
|
29
|
+
const markerLength = info.length + markup.length
|
|
30
|
+
return markerLength > 0 ? markerLength + 1 : 1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
const getLineTokenWithIndent = (line) => {
|
|
34
|
+
if (typeof line !== 'string' || line.length === 0) {
|
|
35
|
+
return null
|
|
36
|
+
}
|
|
37
|
+
const match = line.match(/^([ \t]*)(\S+)(?:\s|$)/)
|
|
38
|
+
if (!match) {
|
|
39
|
+
return null
|
|
40
|
+
}
|
|
41
|
+
return {
|
|
42
|
+
indentWidth: getIndentWidth(match[1]),
|
|
43
|
+
token: match[2]
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const hasLikelyLiteralMarkerToken = (token) => {
|
|
48
|
+
if (typeof token !== 'string' || token.length === 0) {
|
|
49
|
+
return false
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
let core = token
|
|
53
|
+
let hasSuffix = false
|
|
54
|
+
const firstChar = core[0]
|
|
55
|
+
const closingPair = LITERAL_OPEN_CLOSE_PAIRS.get(firstChar)
|
|
56
|
+
if (closingPair) {
|
|
57
|
+
const closeIdx = core.indexOf(closingPair, 1)
|
|
58
|
+
if (closeIdx <= 1) {
|
|
59
|
+
return false
|
|
60
|
+
}
|
|
61
|
+
const inner = core.slice(1, closeIdx)
|
|
62
|
+
const rest = core.slice(closeIdx + 1)
|
|
63
|
+
if (rest.length > 1) {
|
|
64
|
+
return false
|
|
65
|
+
}
|
|
66
|
+
if (rest.length === 1) {
|
|
67
|
+
if (!LITERAL_SUFFIX_CHARS.has(rest)) {
|
|
68
|
+
return false
|
|
69
|
+
}
|
|
70
|
+
hasSuffix = true
|
|
71
|
+
}
|
|
72
|
+
core = inner
|
|
73
|
+
} else {
|
|
74
|
+
const tail = core[core.length - 1]
|
|
75
|
+
if (LITERAL_SUFFIX_CHARS.has(tail)) {
|
|
76
|
+
core = core.slice(0, -1)
|
|
77
|
+
hasSuffix = true
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!core) {
|
|
82
|
+
return false
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// For ASCII tokens, require explicit suffix to avoid treating normal words as list markers.
|
|
86
|
+
if (ASCII_ALNUM_OR_FULLWIDTH_DIGIT_REGEX.test(core)) {
|
|
87
|
+
return hasSuffix
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
// Non-ASCII marker cores (circled digits, kana, kanji, etc.) are typically short.
|
|
91
|
+
return core.length <= 4
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
const hasLikelyLiteralLineHint = (content) => {
|
|
95
|
+
if (typeof content !== 'string' || !content.includes('\n')) {
|
|
96
|
+
return false
|
|
97
|
+
}
|
|
98
|
+
const lines = content.split('\n')
|
|
99
|
+
for (let i = 1; i < lines.length; i++) {
|
|
100
|
+
const line = lines[i]
|
|
101
|
+
if (!line || line.trim().length === 0) {
|
|
102
|
+
continue
|
|
103
|
+
}
|
|
104
|
+
const tokenInfo = getLineTokenWithIndent(line)
|
|
105
|
+
if (!tokenInfo || tokenInfo.indentWidth > MAX_LITERAL_INLINE_INDENT) {
|
|
106
|
+
continue
|
|
107
|
+
}
|
|
108
|
+
if (hasLikelyLiteralMarkerToken(tokenInfo.token)) {
|
|
109
|
+
return true
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
return false
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const hasOverIndentedMarkerLikeLine = (content) => {
|
|
116
|
+
if (typeof content !== 'string' || !content.includes('\n')) {
|
|
117
|
+
return false
|
|
118
|
+
}
|
|
119
|
+
const lines = content.split('\n')
|
|
120
|
+
for (let i = 1; i < lines.length; i++) {
|
|
121
|
+
const line = lines[i]
|
|
122
|
+
if (!line || line.trim().length === 0) {
|
|
123
|
+
continue
|
|
124
|
+
}
|
|
125
|
+
const tokenInfo = getLineTokenWithIndent(line)
|
|
126
|
+
if (!tokenInfo || tokenInfo.indentWidth <= MAX_LITERAL_INLINE_INDENT) {
|
|
127
|
+
continue
|
|
128
|
+
}
|
|
129
|
+
if (hasLikelyLiteralMarkerToken(tokenInfo.token)) {
|
|
130
|
+
return true
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return false
|
|
134
|
+
}
|
|
8
135
|
|
|
9
136
|
/**
|
|
10
137
|
* Normalize literal nested ordered lists inside list items.
|
|
11
138
|
* Converts indented numeric lines into proper ordered_list tokens before Phase 1.
|
|
12
139
|
* @param {Array} tokens
|
|
13
140
|
*/
|
|
14
|
-
export function normalizeLiteralOrderedLists(tokens) {
|
|
141
|
+
export function normalizeLiteralOrderedLists(tokens, opt) {
|
|
142
|
+
if (!opt?.enableLiteralNumberingFix) {
|
|
143
|
+
return
|
|
144
|
+
}
|
|
15
145
|
if (!Array.isArray(tokens) || tokens.length === 0) {
|
|
16
146
|
return
|
|
17
147
|
}
|
|
@@ -21,278 +151,103 @@ export function normalizeLiteralOrderedLists(tokens) {
|
|
|
21
151
|
}
|
|
22
152
|
|
|
23
153
|
let hasInlineLiteralHint = false
|
|
24
|
-
let hasCodeLiteralHint = false
|
|
25
154
|
for (let i = 0; i < tokens.length; i++) {
|
|
26
155
|
const token = tokens[i]
|
|
27
156
|
if (!hasInlineLiteralHint &&
|
|
28
157
|
token.type === 'inline' &&
|
|
29
158
|
token.content &&
|
|
30
|
-
token.content.includes('\n')
|
|
31
|
-
INLINE_LITERAL_HINT.test(token.content)) {
|
|
159
|
+
token.content.includes('\n')) {
|
|
32
160
|
hasInlineLiteralHint = true
|
|
33
|
-
} else if (!hasCodeLiteralHint &&
|
|
34
|
-
token.type === 'code_block' &&
|
|
35
|
-
token.content &&
|
|
36
|
-
CODE_LITERAL_HINT.test(token.content)) {
|
|
37
|
-
hasCodeLiteralHint = true
|
|
38
|
-
}
|
|
39
|
-
if (hasInlineLiteralHint && hasCodeLiteralHint) {
|
|
40
161
|
break
|
|
41
162
|
}
|
|
42
163
|
}
|
|
43
164
|
|
|
44
|
-
if (!hasInlineLiteralHint
|
|
165
|
+
if (!hasInlineLiteralHint) {
|
|
45
166
|
return
|
|
46
167
|
}
|
|
47
168
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
169
|
+
for (let i = 0; i < tokens.length; i++) {
|
|
170
|
+
const token = tokens[i]
|
|
171
|
+
if (token.type !== 'list_item_open') {
|
|
172
|
+
continue
|
|
173
|
+
}
|
|
174
|
+
let listItemClose = findListItemEnd(tokens, i)
|
|
175
|
+
if (listItemClose === -1) {
|
|
176
|
+
listItemClose = tokens.length - 1
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const markerWidth = getListItemMarkerWidth(tokens[i])
|
|
180
|
+
let j = i + 1
|
|
181
|
+
while (j < listItemClose) {
|
|
182
|
+
const current = tokens[j]
|
|
183
|
+
if (current.type === 'paragraph_open') {
|
|
184
|
+
const inlineIdx = j + 1
|
|
185
|
+
const paragraphCloseIdx = j + 2
|
|
186
|
+
if (inlineIdx >= listItemClose ||
|
|
187
|
+
paragraphCloseIdx >= tokens.length ||
|
|
188
|
+
tokens[inlineIdx].type !== 'inline' ||
|
|
189
|
+
tokens[paragraphCloseIdx].type !== 'paragraph_close') {
|
|
63
190
|
j++
|
|
64
191
|
continue
|
|
65
192
|
}
|
|
66
193
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
if (inlineIdx >= listItemClose ||
|
|
71
|
-
paragraphCloseIdx >= tokens.length ||
|
|
72
|
-
tokens[inlineIdx].type !== 'inline' ||
|
|
73
|
-
tokens[paragraphCloseIdx].type !== 'paragraph_close') {
|
|
74
|
-
j++
|
|
75
|
-
continue
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
const inlineToken = tokens[inlineIdx]
|
|
79
|
-
if (!INLINE_LITERAL_HINT.test(inlineToken.content)) {
|
|
80
|
-
j = paragraphCloseIdx + 1
|
|
81
|
-
continue
|
|
82
|
-
}
|
|
83
|
-
const baseLine = tokens[j].map ? tokens[j].map[0] : null
|
|
84
|
-
const segments = parseSegments(inlineToken.content, baseLine)
|
|
85
|
-
if (!segments.hasLiteral) {
|
|
86
|
-
j = paragraphCloseIdx + 1
|
|
87
|
-
continue
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
const listItemLevel = tokens[i].level ?? 0
|
|
91
|
-
const { tokens: replacementTokens, literalListPositions } = buildReplacementTokens(
|
|
92
|
-
segments.list,
|
|
93
|
-
listItemLevel,
|
|
94
|
-
TokenClass,
|
|
95
|
-
tokens[j],
|
|
96
|
-
tokens[inlineIdx],
|
|
97
|
-
tokens[paragraphCloseIdx]
|
|
98
|
-
)
|
|
99
|
-
|
|
100
|
-
const originalLength = paragraphCloseIdx - j + 1
|
|
101
|
-
tokens.splice(j, originalLength, ...replacementTokens)
|
|
102
|
-
|
|
103
|
-
const delta = replacementTokens.length - originalLength
|
|
104
|
-
listItemClose += delta
|
|
105
|
-
|
|
106
|
-
let mergeDelta = 0
|
|
107
|
-
for (const info of literalListPositions) {
|
|
108
|
-
const absoluteIdx = j + info.relativeIndex
|
|
109
|
-
mergeDelta += mergeFollowingLists(tokens, absoluteIdx)
|
|
110
|
-
}
|
|
111
|
-
listItemClose += mergeDelta
|
|
112
|
-
|
|
113
|
-
j = j + replacementTokens.length
|
|
194
|
+
const inlineToken = tokens[inlineIdx]
|
|
195
|
+
if (!inlineToken.content.includes('\n')) {
|
|
196
|
+
j = paragraphCloseIdx + 1
|
|
114
197
|
continue
|
|
115
198
|
}
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
199
|
+
if (!hasLikelyLiteralLineHint(inlineToken.content)) {
|
|
200
|
+
j = paragraphCloseIdx + 1
|
|
201
|
+
continue
|
|
202
|
+
}
|
|
203
|
+
// Be conservative when deeply-indented marker-like lines are present.
|
|
204
|
+
// These lines are often code blocks; partial literal conversion is more surprising
|
|
205
|
+
// than preserving markdown-it's original rendering in this ambiguous case.
|
|
206
|
+
if (hasOverIndentedMarkerLikeLine(inlineToken.content)) {
|
|
207
|
+
j = paragraphCloseIdx + 1
|
|
208
|
+
continue
|
|
209
|
+
}
|
|
210
|
+
const baseLine = tokens[j].map ? tokens[j].map[0] : null
|
|
211
|
+
const segments = parseSegments(inlineToken.content, markerWidth, baseLine)
|
|
212
|
+
if (!segments.hasLiteral) {
|
|
213
|
+
j = paragraphCloseIdx + 1
|
|
122
214
|
continue
|
|
123
215
|
}
|
|
124
216
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
if (!CODE_LITERAL_HINT.test(token.content)) {
|
|
148
|
-
continue
|
|
149
|
-
}
|
|
150
|
-
const rawLines = token.content.split(/\r?\n/)
|
|
151
|
-
const literalCache = []
|
|
152
|
-
const baseLine = Array.isArray(token.map) ? token.map[0] : null
|
|
153
|
-
const { lists, nextIndex } = parseLiteralBlock(rawLines, 0, literalCache, baseLine)
|
|
154
|
-
const hasRemainingContent = rawLines.slice(nextIndex).some(line => line.trim().length > 0)
|
|
155
|
-
if (!Array.isArray(lists) || lists.length === 0 || hasRemainingContent) {
|
|
156
|
-
if (tryConvertCodeBlockContinuation(tokens, i, TokenClass)) {
|
|
157
|
-
i--
|
|
158
|
-
}
|
|
159
|
-
continue
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const targetItemLevel = token.level + 1
|
|
163
|
-
let listItemIdx = -1
|
|
164
|
-
for (let k = i - 1; k >= 0; k--) {
|
|
165
|
-
const tk = tokens[k]
|
|
166
|
-
if (tk.type === 'list_item_open' && tk.level === targetItemLevel) {
|
|
167
|
-
listItemIdx = k
|
|
168
|
-
break
|
|
169
|
-
}
|
|
170
|
-
}
|
|
171
|
-
if (listItemIdx === -1) {
|
|
172
|
-
continue
|
|
173
|
-
}
|
|
174
|
-
let listItemCloseIdx = findMatchingClose(tokens, listItemIdx, 'list_item_open', 'list_item_close')
|
|
175
|
-
if (listItemCloseIdx === -1) {
|
|
176
|
-
continue
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
tokens.splice(i, 1)
|
|
180
|
-
if (listItemCloseIdx > i) {
|
|
181
|
-
listItemCloseIdx--
|
|
182
|
-
}
|
|
183
|
-
revealFirstParagraph(tokens, listItemIdx, listItemCloseIdx)
|
|
184
|
-
|
|
185
|
-
const insertionStart = listItemCloseIdx
|
|
186
|
-
const replacementTokens = []
|
|
187
|
-
const newListLevel = targetItemLevel + 1
|
|
188
|
-
for (const listNode of lists) {
|
|
189
|
-
replacementTokens.push(...buildListTokens(listNode, newListLevel, TokenClass))
|
|
190
|
-
}
|
|
191
|
-
tokens.splice(insertionStart, 0, ...replacementTokens)
|
|
217
|
+
const listItemLevel = tokens[i].level ?? 0
|
|
218
|
+
const { tokens: replacementTokens, literalListPositions } = buildReplacementTokens(
|
|
219
|
+
segments.list,
|
|
220
|
+
listItemLevel,
|
|
221
|
+
TokenClass,
|
|
222
|
+
tokens[j],
|
|
223
|
+
tokens[inlineIdx],
|
|
224
|
+
tokens[paragraphCloseIdx]
|
|
225
|
+
)
|
|
226
|
+
|
|
227
|
+
const originalLength = paragraphCloseIdx - j + 1
|
|
228
|
+
tokens.splice(j, originalLength, ...replacementTokens)
|
|
229
|
+
|
|
230
|
+
const delta = replacementTokens.length - originalLength
|
|
231
|
+
listItemClose += delta
|
|
232
|
+
|
|
233
|
+
let mergeDelta = 0
|
|
234
|
+
for (const info of literalListPositions) {
|
|
235
|
+
const absoluteIdx = j + info.relativeIndex
|
|
236
|
+
mergeDelta += mergeFollowingLists(tokens, absoluteIdx)
|
|
237
|
+
}
|
|
238
|
+
listItemClose += mergeDelta
|
|
192
239
|
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
mergeFollowingLists(tokens, insertionStart + offset)
|
|
240
|
+
j = j + replacementTokens.length
|
|
241
|
+
continue
|
|
196
242
|
}
|
|
197
|
-
}
|
|
198
|
-
i = insertionStart + replacementTokens.length - 1
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
243
|
|
|
202
|
-
|
|
203
|
-
const codeToken = tokens[codeBlockIndex]
|
|
204
|
-
const targetLevel = (codeToken.level ?? 0) + 1
|
|
205
|
-
let listItemCloseIdx = -1
|
|
206
|
-
for (let k = codeBlockIndex - 1; k >= 0; k--) {
|
|
207
|
-
const tk = tokens[k]
|
|
208
|
-
if (tk.type === 'list_item_close' && tk.level === targetLevel) {
|
|
209
|
-
listItemCloseIdx = k
|
|
210
|
-
break
|
|
211
|
-
}
|
|
212
|
-
if (tk.type === 'list_item_open' && tk.level <= codeToken.level) {
|
|
213
|
-
break
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
if (listItemCloseIdx === -1) {
|
|
217
|
-
return false
|
|
218
|
-
}
|
|
219
|
-
let listItemOpenIdx = -1
|
|
220
|
-
let depth = 0
|
|
221
|
-
for (let k = listItemCloseIdx; k >= 0; k--) {
|
|
222
|
-
const tk = tokens[k]
|
|
223
|
-
if (tk.type === 'list_item_close' && tk.level === targetLevel) {
|
|
224
|
-
depth++
|
|
225
|
-
continue
|
|
226
|
-
}
|
|
227
|
-
if (tk.type === 'list_item_open' && tk.level === targetLevel) {
|
|
228
|
-
depth--
|
|
229
|
-
if (depth === 0) {
|
|
230
|
-
listItemOpenIdx = k
|
|
231
|
-
break
|
|
232
|
-
}
|
|
233
|
-
continue
|
|
244
|
+
j++
|
|
234
245
|
}
|
|
235
|
-
|
|
236
|
-
break
|
|
237
|
-
}
|
|
238
|
-
}
|
|
239
|
-
if (listItemOpenIdx === -1) {
|
|
240
|
-
return false
|
|
241
|
-
}
|
|
242
|
-
revealFirstParagraph(tokens, listItemOpenIdx, listItemCloseIdx)
|
|
243
|
-
const paragraphLevel = (tokens[listItemOpenIdx].level ?? 0) + 1
|
|
244
|
-
const paragraphs = buildParagraphTokensFromCodeBlock(codeToken.content, paragraphLevel, TokenClass)
|
|
245
|
-
if (paragraphs.length === 0) {
|
|
246
|
-
return false
|
|
247
|
-
}
|
|
248
|
-
tokens.splice(listItemCloseIdx, 0, ...paragraphs)
|
|
249
|
-
const newIndex = codeBlockIndex + paragraphs.length
|
|
250
|
-
tokens.splice(newIndex, 1)
|
|
251
|
-
return true
|
|
252
|
-
}
|
|
253
|
-
|
|
254
|
-
function buildParagraphTokensFromCodeBlock(content, paragraphLevel, TokenClass) {
|
|
255
|
-
if (!content) {
|
|
256
|
-
return []
|
|
257
|
-
}
|
|
258
|
-
const normalized = content.replace(/\r\n?/g, '\n')
|
|
259
|
-
const lines = normalized.split('\n')
|
|
260
|
-
const nonEmpty = lines.filter(line => line.trim().length > 0)
|
|
261
|
-
if (nonEmpty.length === 0) {
|
|
262
|
-
return []
|
|
263
|
-
}
|
|
264
|
-
const indent = Math.min(...nonEmpty.map(line => line.match(/^\s*/)[0].length))
|
|
265
|
-
const dedented = lines
|
|
266
|
-
.map(line => (indent > 0 ? line.slice(Math.min(indent, line.length)) : line))
|
|
267
|
-
.join('\n')
|
|
268
|
-
const blocks = dedented.split(/\n{2,}/).map(block => block.trim()).filter(Boolean)
|
|
269
|
-
if (blocks.length === 0) {
|
|
270
|
-
return []
|
|
246
|
+
i = listItemClose
|
|
271
247
|
}
|
|
272
|
-
const paragraphs = []
|
|
273
|
-
for (const block of blocks) {
|
|
274
|
-
const pOpen = new TokenClass('paragraph_open', 'p', 1)
|
|
275
|
-
pOpen.level = paragraphLevel
|
|
276
|
-
pOpen.block = true
|
|
277
|
-
pOpen.hidden = false
|
|
278
|
-
paragraphs.push(pOpen)
|
|
279
|
-
|
|
280
|
-
const inline = new TokenClass('inline', '', 0)
|
|
281
|
-
inline.level = paragraphLevel + 1
|
|
282
|
-
inline.content = block
|
|
283
|
-
inline.children = []
|
|
284
|
-
paragraphs.push(inline)
|
|
285
|
-
|
|
286
|
-
const pClose = new TokenClass('paragraph_close', 'p', -1)
|
|
287
|
-
pClose.level = paragraphLevel
|
|
288
|
-
pClose.block = true
|
|
289
|
-
pClose.hidden = false
|
|
290
|
-
paragraphs.push(pClose)
|
|
291
|
-
}
|
|
292
|
-
return paragraphs
|
|
293
248
|
}
|
|
294
249
|
|
|
295
|
-
function parseSegments(content, baseLine = null) {
|
|
250
|
+
function parseSegments(content, markerWidth, baseLine = null) {
|
|
296
251
|
if (!content) {
|
|
297
252
|
return { hasLiteral: false, list: [{ type: 'text', text: '', tight: false }] }
|
|
298
253
|
}
|
|
@@ -318,14 +273,21 @@ function parseSegments(content, baseLine = null) {
|
|
|
318
273
|
}
|
|
319
274
|
}
|
|
320
275
|
const textValue = buffer.join('\n')
|
|
321
|
-
|
|
322
|
-
segments.push({ type: 'text', text: textValue, tight: !hasBlankLine })
|
|
276
|
+
segments.push({ type: 'text', text: textValue, tight: !hadBlankLine })
|
|
323
277
|
buffer = []
|
|
324
278
|
blankLinesInBuffer = 0
|
|
325
279
|
}
|
|
326
280
|
|
|
327
281
|
while (idx < lines.length) {
|
|
328
|
-
const
|
|
282
|
+
const isFirstLine = idx === 0
|
|
283
|
+
const trimmedLine = lines[idx].trim()
|
|
284
|
+
if (isFirstLine && trimmedLine.length > 0) {
|
|
285
|
+
buffer.push(lines[idx])
|
|
286
|
+
idx++
|
|
287
|
+
continue
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const literalInfo = getLiteralInfo(lines, idx, literalCache, markerWidth)
|
|
329
291
|
if (!literalInfo) {
|
|
330
292
|
buffer.push(lines[idx])
|
|
331
293
|
if (lines[idx].trim().length === 0) {
|
|
@@ -337,7 +299,7 @@ function parseSegments(content, baseLine = null) {
|
|
|
337
299
|
|
|
338
300
|
hasLiteral = true
|
|
339
301
|
flushBuffer({ trimTrailing: true })
|
|
340
|
-
const { lists, nextIndex } = parseLiteralBlock(lines, idx, literalCache, baseLine)
|
|
302
|
+
const { lists, nextIndex } = parseLiteralBlock(lines, idx, literalCache, markerWidth, baseLine)
|
|
341
303
|
if (lists.length > 0) {
|
|
342
304
|
segments.push({ type: 'literal', lists })
|
|
343
305
|
}
|
|
@@ -348,20 +310,20 @@ function parseSegments(content, baseLine = null) {
|
|
|
348
310
|
return { hasLiteral, list: segments }
|
|
349
311
|
}
|
|
350
312
|
|
|
351
|
-
function detectLiteralLine(line) {
|
|
313
|
+
function detectLiteralLine(line, markerWidth) {
|
|
352
314
|
if (!line) return null
|
|
353
315
|
if (/^\s*$/.test(line)) {
|
|
354
316
|
return null
|
|
355
317
|
}
|
|
356
|
-
const match = line.match(/^([ \t]
|
|
318
|
+
const match = line.match(/^([ \t]*)(\S.*)$/)
|
|
357
319
|
if (!match) {
|
|
358
320
|
return null
|
|
359
321
|
}
|
|
360
|
-
const indentWidth = match[1]
|
|
361
|
-
if (indentWidth
|
|
322
|
+
const indentWidth = getIndentWidth(match[1])
|
|
323
|
+
if (indentWidth > MAX_LITERAL_INLINE_INDENT) {
|
|
362
324
|
return null
|
|
363
325
|
}
|
|
364
|
-
const trimmed = match[2]
|
|
326
|
+
const trimmed = match[2]
|
|
365
327
|
if (!trimmed) {
|
|
366
328
|
return null
|
|
367
329
|
}
|
|
@@ -373,14 +335,15 @@ function detectLiteralLine(line) {
|
|
|
373
335
|
return null
|
|
374
336
|
}
|
|
375
337
|
const remainder = trimmed.slice(markerInfo.marker.length).replace(/^\s+/, '')
|
|
338
|
+
const safeMarkerWidth = Number.isFinite(markerWidth) ? markerWidth : 1
|
|
376
339
|
return {
|
|
377
|
-
indent: indentWidth,
|
|
340
|
+
indent: safeMarkerWidth + indentWidth,
|
|
378
341
|
markerInfo,
|
|
379
342
|
content: remainder
|
|
380
343
|
}
|
|
381
344
|
}
|
|
382
345
|
|
|
383
|
-
function parseLiteralBlock(lines, startIndex, literalCache = null, baseLine = null) {
|
|
346
|
+
function parseLiteralBlock(lines, startIndex, literalCache = null, markerWidth = 1, baseLine = null) {
|
|
384
347
|
const rootLists = []
|
|
385
348
|
const stack = []
|
|
386
349
|
let idx = startIndex
|
|
@@ -399,7 +362,7 @@ function parseLiteralBlock(lines, startIndex, literalCache = null, baseLine = nu
|
|
|
399
362
|
continue
|
|
400
363
|
}
|
|
401
364
|
|
|
402
|
-
const literalInfo = getLiteralInfo(lines, idx, literalCache)
|
|
365
|
+
const literalInfo = getLiteralInfo(lines, idx, literalCache, markerWidth)
|
|
403
366
|
if (!literalInfo) {
|
|
404
367
|
break
|
|
405
368
|
}
|
|
@@ -436,13 +399,15 @@ function parseLiteralBlock(lines, startIndex, literalCache = null, baseLine = nu
|
|
|
436
399
|
break
|
|
437
400
|
}
|
|
438
401
|
|
|
402
|
+
const lineNumber = typeof baseLine === 'number' ? baseLine + idx : null
|
|
439
403
|
currentList.items.push({
|
|
440
404
|
markerInfo: literalInfo.markerInfo,
|
|
441
405
|
content: literalInfo.content,
|
|
442
|
-
children: []
|
|
406
|
+
children: [],
|
|
407
|
+
line: lineNumber
|
|
443
408
|
})
|
|
444
|
-
if (
|
|
445
|
-
currentList.lastLine =
|
|
409
|
+
if (typeof lineNumber === 'number') {
|
|
410
|
+
currentList.lastLine = lineNumber
|
|
446
411
|
}
|
|
447
412
|
idx++
|
|
448
413
|
}
|
|
@@ -450,12 +415,12 @@ function parseLiteralBlock(lines, startIndex, literalCache = null, baseLine = nu
|
|
|
450
415
|
return { lists: rootLists, nextIndex: idx }
|
|
451
416
|
}
|
|
452
417
|
|
|
453
|
-
function getLiteralInfo(lines, index, cache = null) {
|
|
418
|
+
function getLiteralInfo(lines, index, cache = null, markerWidth = 1) {
|
|
454
419
|
if (!cache) {
|
|
455
|
-
return detectLiteralLine(lines[index])
|
|
420
|
+
return detectLiteralLine(lines[index], markerWidth)
|
|
456
421
|
}
|
|
457
422
|
if (cache[index] === undefined) {
|
|
458
|
-
cache[index] = detectLiteralLine(lines[index]) || null
|
|
423
|
+
cache[index] = detectLiteralLine(lines[index], markerWidth) || null
|
|
459
424
|
}
|
|
460
425
|
return cache[index]
|
|
461
426
|
}
|
|
@@ -478,6 +443,7 @@ function buildReplacementTokens(segments, listItemLevel, TokenClass, paragraphOp
|
|
|
478
443
|
const tokens = []
|
|
479
444
|
const literalListPositions = []
|
|
480
445
|
let templateUsed = false
|
|
446
|
+
const paragraphMap = paragraphOpen?.map ? paragraphOpen.map.slice() : null
|
|
481
447
|
|
|
482
448
|
for (const segment of segments) {
|
|
483
449
|
if (segment.type === 'text') {
|
|
@@ -486,7 +452,7 @@ function buildReplacementTokens(segments, listItemLevel, TokenClass, paragraphOp
|
|
|
486
452
|
continue
|
|
487
453
|
}
|
|
488
454
|
const template = !templateUsed ? { open: paragraphOpen, inline: inlineToken, close: paragraphClose } : null
|
|
489
|
-
tokens.push(...createParagraphTokens(segment.text, listItemLevel, TokenClass, template, segment.tight))
|
|
455
|
+
tokens.push(...createParagraphTokens(segment.text, listItemLevel, TokenClass, template, segment.tight, paragraphMap))
|
|
490
456
|
templateUsed = true
|
|
491
457
|
} else if (segment.type === 'literal') {
|
|
492
458
|
for (const listNode of segment.lists) {
|
|
@@ -501,7 +467,7 @@ function buildReplacementTokens(segments, listItemLevel, TokenClass, paragraphOp
|
|
|
501
467
|
return { tokens, literalListPositions }
|
|
502
468
|
}
|
|
503
469
|
|
|
504
|
-
function createParagraphTokens(text, listItemLevel, TokenClass, template, forceTight = false) {
|
|
470
|
+
function createParagraphTokens(text, listItemLevel, TokenClass, template, forceTight = false, mapFallback = null) {
|
|
505
471
|
if (!text) {
|
|
506
472
|
return []
|
|
507
473
|
}
|
|
@@ -510,6 +476,9 @@ function createParagraphTokens(text, listItemLevel, TokenClass, template, forceT
|
|
|
510
476
|
const open = template ? cloneToken(template.open) : new TokenClass('paragraph_open', 'p', 1)
|
|
511
477
|
open.level = level
|
|
512
478
|
open.block = true
|
|
479
|
+
if (Array.isArray(mapFallback) && !open.map) {
|
|
480
|
+
open.map = mapFallback.slice()
|
|
481
|
+
}
|
|
513
482
|
const baseHidden = template && typeof template.open?.hidden === 'boolean' ? template.open.hidden : false
|
|
514
483
|
const shouldHide = baseHidden
|
|
515
484
|
open.hidden = shouldHide
|
|
@@ -536,6 +505,9 @@ function createParagraphTokens(text, listItemLevel, TokenClass, template, forceT
|
|
|
536
505
|
close.level = level
|
|
537
506
|
close.block = true
|
|
538
507
|
close.hidden = shouldHide
|
|
508
|
+
if (Array.isArray(mapFallback) && !close.map) {
|
|
509
|
+
close.map = mapFallback.slice()
|
|
510
|
+
}
|
|
539
511
|
if (forceTight) {
|
|
540
512
|
close._literalTight = true
|
|
541
513
|
}
|
|
@@ -551,6 +523,47 @@ function buildListTokens(listNode, listLevel, TokenClass) {
|
|
|
551
523
|
listOpen.markup = listNode.suffix || '.'
|
|
552
524
|
listOpen.attrs = null
|
|
553
525
|
listOpen._literalList = true
|
|
526
|
+
const listMap = buildLineMap(listNode.startLine, listNode.lastLine)
|
|
527
|
+
if (listMap) {
|
|
528
|
+
listOpen.map = listMap.slice()
|
|
529
|
+
}
|
|
530
|
+
if (Array.isArray(listNode.items) && listNode.items.length > 0) {
|
|
531
|
+
const markers = listNode.items
|
|
532
|
+
.map(item => {
|
|
533
|
+
if (!item.markerInfo) {
|
|
534
|
+
return null
|
|
535
|
+
}
|
|
536
|
+
const marker = { ...item.markerInfo }
|
|
537
|
+
if (typeof marker.originalNumber !== 'number' && typeof marker.number === 'number') {
|
|
538
|
+
marker.originalNumber = marker.number
|
|
539
|
+
}
|
|
540
|
+
return marker
|
|
541
|
+
})
|
|
542
|
+
.filter(Boolean)
|
|
543
|
+
if (markers.length === listNode.items.length) {
|
|
544
|
+
const firstType = markers[0].type
|
|
545
|
+
const isConsistent = markers.every(marker => marker.type === firstType)
|
|
546
|
+
const literalNumbers = markers.map(marker =>
|
|
547
|
+
typeof marker.originalNumber === 'number' ? marker.originalNumber : marker.number
|
|
548
|
+
)
|
|
549
|
+
const hasLiteralNumbers = literalNumbers.length === markers.length &&
|
|
550
|
+
literalNumbers.every(value => typeof value === 'number')
|
|
551
|
+
let allNumbersIdentical = false
|
|
552
|
+
if (hasLiteralNumbers) {
|
|
553
|
+
const firstLiteral = literalNumbers[0]
|
|
554
|
+
if (literalNumbers.every(value => value === firstLiteral) && firstLiteral === 1) {
|
|
555
|
+
allNumbersIdentical = true
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
listOpen._literalMarkerInfo = {
|
|
559
|
+
markers,
|
|
560
|
+
type: firstType,
|
|
561
|
+
isConsistent,
|
|
562
|
+
count: markers.length,
|
|
563
|
+
allNumbersIdentical
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
554
567
|
if (typeof listNode.startLine === 'number') {
|
|
555
568
|
listOpen._literalStartLine = listNode.startLine
|
|
556
569
|
}
|
|
@@ -571,6 +584,9 @@ function buildListTokens(listNode, listLevel, TokenClass) {
|
|
|
571
584
|
listClose.level = listLevel
|
|
572
585
|
listClose.block = true
|
|
573
586
|
listClose.markup = listNode.suffix || '.'
|
|
587
|
+
if (listMap) {
|
|
588
|
+
listClose.map = listMap.slice()
|
|
589
|
+
}
|
|
574
590
|
tokens.push(listClose)
|
|
575
591
|
return tokens
|
|
576
592
|
}
|
|
@@ -583,6 +599,10 @@ function buildListItemTokens(item, listLevel, TokenClass, parentListIsLoose = fa
|
|
|
583
599
|
liOpen.block = true
|
|
584
600
|
liOpen.markup = item.markerInfo?.suffix || '.'
|
|
585
601
|
liOpen.info = item.markerInfo?.number !== undefined ? String(item.markerInfo.number) : ''
|
|
602
|
+
const itemMap = buildLineMap(item.line, item.line)
|
|
603
|
+
if (itemMap) {
|
|
604
|
+
liOpen.map = itemMap.slice()
|
|
605
|
+
}
|
|
586
606
|
tokens.push(liOpen)
|
|
587
607
|
|
|
588
608
|
const paragraphLevel = itemLevel + 1
|
|
@@ -593,6 +613,9 @@ function buildListItemTokens(item, listLevel, TokenClass, parentListIsLoose = fa
|
|
|
593
613
|
if (!parentListIsLoose) {
|
|
594
614
|
pOpen._literalTight = true
|
|
595
615
|
}
|
|
616
|
+
if (itemMap) {
|
|
617
|
+
pOpen.map = itemMap.slice()
|
|
618
|
+
}
|
|
596
619
|
tokens.push(pOpen)
|
|
597
620
|
|
|
598
621
|
const inline = new TokenClass('inline', '', 0)
|
|
@@ -608,6 +631,9 @@ function buildListItemTokens(item, listLevel, TokenClass, parentListIsLoose = fa
|
|
|
608
631
|
if (!parentListIsLoose) {
|
|
609
632
|
pClose._literalTight = true
|
|
610
633
|
}
|
|
634
|
+
if (itemMap) {
|
|
635
|
+
pClose.map = itemMap.slice()
|
|
636
|
+
}
|
|
611
637
|
tokens.push(pClose)
|
|
612
638
|
|
|
613
639
|
if (item.children && item.children.length > 0) {
|
|
@@ -619,6 +645,9 @@ function buildListItemTokens(item, listLevel, TokenClass, parentListIsLoose = fa
|
|
|
619
645
|
const liClose = new TokenClass('list_item_close', 'li', -1)
|
|
620
646
|
liClose.level = itemLevel
|
|
621
647
|
liClose.block = true
|
|
648
|
+
if (itemMap) {
|
|
649
|
+
liClose.map = itemMap.slice()
|
|
650
|
+
}
|
|
622
651
|
tokens.push(liClose)
|
|
623
652
|
return tokens
|
|
624
653
|
}
|
|
@@ -642,22 +671,6 @@ function cloneToken(token) {
|
|
|
642
671
|
return cloned
|
|
643
672
|
}
|
|
644
673
|
|
|
645
|
-
function revealFirstParagraph(tokens, listItemOpenIdx, listItemCloseIdx) {
|
|
646
|
-
const paragraphLevel = (tokens[listItemOpenIdx].level ?? 0) + 1
|
|
647
|
-
for (let k = listItemOpenIdx + 1; k < listItemCloseIdx; k++) {
|
|
648
|
-
if (tokens[k].type === 'paragraph_open' && tokens[k].level === paragraphLevel) {
|
|
649
|
-
tokens[k].hidden = false
|
|
650
|
-
for (let m = k + 1; m < listItemCloseIdx; m++) {
|
|
651
|
-
if (tokens[m].type === 'paragraph_close' && tokens[m].level === paragraphLevel) {
|
|
652
|
-
tokens[m].hidden = false
|
|
653
|
-
break
|
|
654
|
-
}
|
|
655
|
-
}
|
|
656
|
-
break
|
|
657
|
-
}
|
|
658
|
-
}
|
|
659
|
-
}
|
|
660
|
-
|
|
661
674
|
function mergeFollowingLists(tokens, listOpenIndex) {
|
|
662
675
|
if (listOpenIndex < 0 || listOpenIndex >= tokens.length) {
|
|
663
676
|
return 0
|
|
@@ -737,83 +750,3 @@ function markLiteralListLoose(tokens, listOpenIndex, listCloseIndex = null) {
|
|
|
737
750
|
}
|
|
738
751
|
}
|
|
739
752
|
}
|
|
740
|
-
|
|
741
|
-
function splitOrderedListForLiteralChildren(tokens, listOpenIndex, TokenClass) {
|
|
742
|
-
const listToken = tokens[listOpenIndex]
|
|
743
|
-
const listCloseIndex = findMatchingClose(tokens, listOpenIndex, 'ordered_list_open', 'ordered_list_close')
|
|
744
|
-
if (listCloseIndex === -1) {
|
|
745
|
-
return 0
|
|
746
|
-
}
|
|
747
|
-
const childLevel = (listToken.level ?? 0) + 1
|
|
748
|
-
const ranges = []
|
|
749
|
-
let currentOpen = -1
|
|
750
|
-
for (let idx = listOpenIndex + 1; idx < listCloseIndex; idx++) {
|
|
751
|
-
const token = tokens[idx]
|
|
752
|
-
if (token.type === 'list_item_open' && token.level === childLevel) {
|
|
753
|
-
currentOpen = idx
|
|
754
|
-
continue
|
|
755
|
-
}
|
|
756
|
-
if (token.type === 'list_item_close' && token.level === childLevel) {
|
|
757
|
-
if (currentOpen !== -1) {
|
|
758
|
-
ranges.push({ open: currentOpen, close: idx })
|
|
759
|
-
currentOpen = -1
|
|
760
|
-
}
|
|
761
|
-
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
if (ranges.length <= 1) {
|
|
765
|
-
return 0
|
|
766
|
-
}
|
|
767
|
-
|
|
768
|
-
const extraRanges = ranges.slice(1)
|
|
769
|
-
const childTokens = []
|
|
770
|
-
let removedCount = 0
|
|
771
|
-
|
|
772
|
-
for (const range of extraRanges) {
|
|
773
|
-
childTokens.push(...tokens.slice(range.open, range.close + 1))
|
|
774
|
-
}
|
|
775
|
-
|
|
776
|
-
for (let k = extraRanges.length - 1; k >= 0; k--) {
|
|
777
|
-
const range = extraRanges[k]
|
|
778
|
-
const len = range.close - range.open + 1
|
|
779
|
-
tokens.splice(range.open, len)
|
|
780
|
-
removedCount += len
|
|
781
|
-
}
|
|
782
|
-
|
|
783
|
-
if (listToken._markerInfo) {
|
|
784
|
-
delete listToken._markerInfo
|
|
785
|
-
}
|
|
786
|
-
|
|
787
|
-
const levelShift = 2
|
|
788
|
-
for (const token of childTokens) {
|
|
789
|
-
if (typeof token.level === 'number') {
|
|
790
|
-
token.level += levelShift
|
|
791
|
-
}
|
|
792
|
-
}
|
|
793
|
-
|
|
794
|
-
const nestedListOpen = new TokenClass('ordered_list_open', 'ol', 1)
|
|
795
|
-
nestedListOpen.level = (listToken.level ?? 0) + 2
|
|
796
|
-
nestedListOpen.block = true
|
|
797
|
-
nestedListOpen.markup = listToken.markup
|
|
798
|
-
nestedListOpen.attrs = null
|
|
799
|
-
nestedListOpen._literalList = true
|
|
800
|
-
|
|
801
|
-
const firstChild = childTokens.find(t => t.type === 'list_item_open')
|
|
802
|
-
if (firstChild?.info) {
|
|
803
|
-
const num = parseInt(firstChild.info, 10)
|
|
804
|
-
if (!Number.isNaN(num) && num !== 1) {
|
|
805
|
-
nestedListOpen.attrs = [['start', String(num)]]
|
|
806
|
-
}
|
|
807
|
-
}
|
|
808
|
-
|
|
809
|
-
const nestedListClose = new TokenClass('ordered_list_close', 'ol', -1)
|
|
810
|
-
nestedListClose.level = nestedListOpen.level
|
|
811
|
-
nestedListClose.block = true
|
|
812
|
-
nestedListClose.markup = listToken.markup
|
|
813
|
-
|
|
814
|
-
const insertionIndex = ranges[0].close
|
|
815
|
-
tokens.splice(insertionIndex, 0, nestedListOpen, ...childTokens, nestedListClose)
|
|
816
|
-
|
|
817
|
-
const addedCount = childTokens.length + 2
|
|
818
|
-
return addedCount - removedCount
|
|
819
|
-
}
|