@peaceroad/markdown-it-numbering-ul-regarded-as-ol 0.2.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,776 @@
1
+ // Normalize literal ordered-list lines that markdown-it failed to parse
2
+ // e.g. nested "2. Child" without preceding "- ".
3
+ import { detectMarkerType } from './types-utility.js'
4
+ import { findMatchingClose, findListItemEnd } from './list-helpers.js'
5
+
6
+ const INLINE_LITERAL_HINT = /\n[ \t]+\S/
7
+ const CODE_LITERAL_HINT = /^[ \t]+\S/m
8
+
9
+ /**
10
+ * Normalize literal nested ordered lists inside list items.
11
+ * Converts indented numeric lines into proper ordered_list tokens before Phase 1.
12
+ * @param {Array} tokens
13
+ */
14
+ export function normalizeLiteralOrderedLists(tokens) {
15
+ if (!Array.isArray(tokens) || tokens.length === 0) {
16
+ return
17
+ }
18
+ const TokenClass = tokens[0]?.constructor
19
+ if (!TokenClass) {
20
+ return
21
+ }
22
+
23
+ for (let i = 0; i < tokens.length; i++) {
24
+ const token = tokens[i]
25
+ if (token.type !== 'list_item_open') {
26
+ continue
27
+ }
28
+ let listItemClose = findListItemEnd(tokens, i)
29
+ if (listItemClose === -1) {
30
+ listItemClose = tokens.length - 1
31
+ }
32
+
33
+ let j = i + 1
34
+ while (j < listItemClose) {
35
+ const current = tokens[j]
36
+ if (current.type !== 'paragraph_open') {
37
+ j++
38
+ continue
39
+ }
40
+
41
+ if (current.type === 'paragraph_open') {
42
+ const inlineIdx = j + 1
43
+ const paragraphCloseIdx = j + 2
44
+ if (inlineIdx >= listItemClose ||
45
+ paragraphCloseIdx >= tokens.length ||
46
+ tokens[inlineIdx].type !== 'inline' ||
47
+ tokens[paragraphCloseIdx].type !== 'paragraph_close') {
48
+ j++
49
+ continue
50
+ }
51
+
52
+ const inlineToken = tokens[inlineIdx]
53
+ if (!INLINE_LITERAL_HINT.test(inlineToken.content)) {
54
+ j = paragraphCloseIdx + 1
55
+ continue
56
+ }
57
+ const baseLine = tokens[j].map ? tokens[j].map[0] : null
58
+ const segments = parseSegments(inlineToken.content, baseLine)
59
+ if (!segments.hasLiteral) {
60
+ j = paragraphCloseIdx + 1
61
+ continue
62
+ }
63
+
64
+ const listItemLevel = tokens[i].level ?? 0
65
+ const { tokens: replacementTokens, literalListPositions } = buildReplacementTokens(
66
+ segments.list,
67
+ listItemLevel,
68
+ TokenClass,
69
+ tokens[j],
70
+ tokens[inlineIdx],
71
+ tokens[paragraphCloseIdx]
72
+ )
73
+
74
+ const originalLength = paragraphCloseIdx - j + 1
75
+ tokens.splice(j, originalLength, ...replacementTokens)
76
+
77
+ const delta = replacementTokens.length - originalLength
78
+ listItemClose += delta
79
+
80
+ let mergeDelta = 0
81
+ for (const info of literalListPositions) {
82
+ const absoluteIdx = j + info.relativeIndex
83
+ mergeDelta += mergeFollowingLists(tokens, absoluteIdx)
84
+ }
85
+ listItemClose += mergeDelta
86
+
87
+ j = j + replacementTokens.length
88
+ continue
89
+ }
90
+
91
+ if (current.type === 'ordered_list_open' &&
92
+ current.level === (tokens[i].level ?? 0) + 1) {
93
+ const delta = splitOrderedListForLiteralChildren(tokens, j, TokenClass)
94
+ listItemClose += delta
95
+ j++
96
+ continue
97
+ }
98
+
99
+ j++
100
+ }
101
+ i = listItemClose
102
+ }
103
+ convertLiteralCodeBlocks(tokens, TokenClass)
104
+ }
105
+
106
+ function convertLiteralCodeBlocks(tokens, TokenClass) {
107
+ if (!TokenClass) {
108
+ return
109
+ }
110
+ for (let i = 0; i < tokens.length; i++) {
111
+ const token = tokens[i]
112
+ if (!token || token.type !== 'code_block') {
113
+ continue
114
+ }
115
+ if (token.level === undefined || token.level < 1) {
116
+ continue
117
+ }
118
+ if (!CODE_LITERAL_HINT.test(token.content)) {
119
+ continue
120
+ }
121
+ const rawLines = token.content.split(/\r?\n/)
122
+ const literalCache = []
123
+ const baseLine = Array.isArray(token.map) ? token.map[0] : null
124
+ const { lists, nextIndex } = parseLiteralBlock(rawLines, 0, literalCache, baseLine)
125
+ const hasRemainingContent = rawLines.slice(nextIndex).some(line => line.trim().length > 0)
126
+ if (!Array.isArray(lists) || lists.length === 0 || hasRemainingContent) {
127
+ if (tryConvertCodeBlockContinuation(tokens, i, TokenClass)) {
128
+ i--
129
+ }
130
+ continue
131
+ }
132
+
133
+ const targetItemLevel = token.level + 1
134
+ let listItemIdx = -1
135
+ for (let k = i - 1; k >= 0; k--) {
136
+ const tk = tokens[k]
137
+ if (tk.type === 'list_item_open' && tk.level === targetItemLevel) {
138
+ listItemIdx = k
139
+ break
140
+ }
141
+ }
142
+ if (listItemIdx === -1) {
143
+ continue
144
+ }
145
+ let listItemCloseIdx = findMatchingClose(tokens, listItemIdx, 'list_item_open', 'list_item_close')
146
+ if (listItemCloseIdx === -1) {
147
+ continue
148
+ }
149
+
150
+ tokens.splice(i, 1)
151
+ if (listItemCloseIdx > i) {
152
+ listItemCloseIdx--
153
+ }
154
+ revealFirstParagraph(tokens, listItemIdx, listItemCloseIdx)
155
+
156
+ const insertionStart = listItemCloseIdx
157
+ const replacementTokens = []
158
+ const newListLevel = targetItemLevel + 1
159
+ for (const listNode of lists) {
160
+ replacementTokens.push(...buildListTokens(listNode, newListLevel, TokenClass))
161
+ }
162
+ tokens.splice(insertionStart, 0, ...replacementTokens)
163
+
164
+ for (let offset = 0; offset < replacementTokens.length; offset++) {
165
+ if (replacementTokens[offset]?._literalList) {
166
+ mergeFollowingLists(tokens, insertionStart + offset)
167
+ }
168
+ }
169
+ i = insertionStart + replacementTokens.length - 1
170
+ }
171
+ }
172
+
173
+ function tryConvertCodeBlockContinuation(tokens, codeBlockIndex, TokenClass) {
174
+ const codeToken = tokens[codeBlockIndex]
175
+ const targetLevel = (codeToken.level ?? 0) + 1
176
+ let listItemCloseIdx = -1
177
+ for (let k = codeBlockIndex - 1; k >= 0; k--) {
178
+ const tk = tokens[k]
179
+ if (tk.type === 'list_item_close' && tk.level === targetLevel) {
180
+ listItemCloseIdx = k
181
+ break
182
+ }
183
+ if (tk.type === 'list_item_open' && tk.level <= codeToken.level) {
184
+ break
185
+ }
186
+ }
187
+ if (listItemCloseIdx === -1) {
188
+ return false
189
+ }
190
+ let listItemOpenIdx = -1
191
+ for (let k = listItemCloseIdx; k >= 0; k--) {
192
+ if (tokens[k].type === 'list_item_open' && tokens[k].level === targetLevel) {
193
+ const closeIdx = findListItemEnd(tokens, k)
194
+ if (closeIdx === listItemCloseIdx) {
195
+ listItemOpenIdx = k
196
+ break
197
+ }
198
+ }
199
+ }
200
+ if (listItemOpenIdx === -1) {
201
+ return false
202
+ }
203
+ revealFirstParagraph(tokens, listItemOpenIdx, listItemCloseIdx)
204
+ const paragraphLevel = (tokens[listItemOpenIdx].level ?? 0) + 1
205
+ const paragraphs = buildParagraphTokensFromCodeBlock(codeToken.content, paragraphLevel, TokenClass)
206
+ if (paragraphs.length === 0) {
207
+ return false
208
+ }
209
+ tokens.splice(listItemCloseIdx, 0, ...paragraphs)
210
+ const newIndex = codeBlockIndex + paragraphs.length
211
+ tokens.splice(newIndex, 1)
212
+ return true
213
+ }
214
+
215
+ function buildParagraphTokensFromCodeBlock(content, paragraphLevel, TokenClass) {
216
+ if (!content) {
217
+ return []
218
+ }
219
+ const normalized = content.replace(/\r\n?/g, '\n')
220
+ const lines = normalized.split('\n')
221
+ const nonEmpty = lines.filter(line => line.trim().length > 0)
222
+ if (nonEmpty.length === 0) {
223
+ return []
224
+ }
225
+ const indent = Math.min(...nonEmpty.map(line => line.match(/^\s*/)[0].length))
226
+ const dedented = lines
227
+ .map(line => (indent > 0 ? line.slice(Math.min(indent, line.length)) : line))
228
+ .join('\n')
229
+ const blocks = dedented.split(/\n{2,}/).map(block => block.trim()).filter(Boolean)
230
+ if (blocks.length === 0) {
231
+ return []
232
+ }
233
+ const paragraphs = []
234
+ for (const block of blocks) {
235
+ const pOpen = new TokenClass('paragraph_open', 'p', 1)
236
+ pOpen.level = paragraphLevel
237
+ pOpen.block = true
238
+ pOpen.hidden = false
239
+ paragraphs.push(pOpen)
240
+
241
+ const inline = new TokenClass('inline', '', 0)
242
+ inline.level = paragraphLevel + 1
243
+ inline.content = block
244
+ inline.children = []
245
+ paragraphs.push(inline)
246
+
247
+ const pClose = new TokenClass('paragraph_close', 'p', -1)
248
+ pClose.level = paragraphLevel
249
+ pClose.block = true
250
+ pClose.hidden = false
251
+ paragraphs.push(pClose)
252
+ }
253
+ return paragraphs
254
+ }
255
+
256
+ function parseSegments(content, baseLine = null) {
257
+ if (!content) {
258
+ return { hasLiteral: false, list: [{ type: 'text', text: '', tight: false }] }
259
+ }
260
+
261
+ const lines = content.split('\n')
262
+ const literalCache = new Array(lines.length)
263
+ const segments = []
264
+ let buffer = []
265
+ let blankLinesInBuffer = 0
266
+ let hasLiteral = false
267
+ let idx = 0
268
+
269
+ const flushBuffer = ({ trimTrailing = false } = {}) => {
270
+ if (buffer.length === 0) return
271
+ const hadBlankLine = blankLinesInBuffer > 0
272
+ if (trimTrailing) {
273
+ while (buffer.length > 0 && buffer[buffer.length - 1].trim().length === 0) {
274
+ buffer.pop()
275
+ blankLinesInBuffer = Math.max(0, blankLinesInBuffer - 1)
276
+ }
277
+ if (buffer.length === 0) {
278
+ return
279
+ }
280
+ }
281
+ const textValue = buffer.join('\n')
282
+ const hasBlankLine = hadBlankLine
283
+ segments.push({ type: 'text', text: textValue, tight: !hasBlankLine })
284
+ buffer = []
285
+ blankLinesInBuffer = 0
286
+ }
287
+
288
+ while (idx < lines.length) {
289
+ const literalInfo = getLiteralInfo(lines, idx, literalCache)
290
+ if (!literalInfo) {
291
+ buffer.push(lines[idx])
292
+ if (lines[idx].trim().length === 0) {
293
+ blankLinesInBuffer++
294
+ }
295
+ idx++
296
+ continue
297
+ }
298
+
299
+ hasLiteral = true
300
+ flushBuffer({ trimTrailing: true })
301
+ const { lists, nextIndex } = parseLiteralBlock(lines, idx, literalCache, baseLine)
302
+ if (lists.length > 0) {
303
+ segments.push({ type: 'literal', lists })
304
+ }
305
+ idx = nextIndex
306
+ }
307
+
308
+ flushBuffer()
309
+ return { hasLiteral, list: segments }
310
+ }
311
+
312
+ function detectLiteralLine(line) {
313
+ if (!line) return null
314
+ if (/^\s*$/.test(line)) {
315
+ return null
316
+ }
317
+ const match = line.match(/^([ \t]+)(.*)$/)
318
+ if (!match) {
319
+ return null
320
+ }
321
+ const indentWidth = match[1].replace(/\t/g, ' ').length
322
+ if (indentWidth < 1) {
323
+ return null
324
+ }
325
+ const trimmed = match[2].trimStart()
326
+ if (!trimmed) {
327
+ return null
328
+ }
329
+ const markerInfo = detectMarkerType(trimmed)
330
+ if (!markerInfo || !markerInfo.marker) {
331
+ return null
332
+ }
333
+ if (!trimmed.startsWith(markerInfo.marker)) {
334
+ return null
335
+ }
336
+ const remainder = trimmed.slice(markerInfo.marker.length).replace(/^\s+/, '')
337
+ return {
338
+ indent: indentWidth,
339
+ markerInfo,
340
+ content: remainder
341
+ }
342
+ }
343
+
344
+ function parseLiteralBlock(lines, startIndex, literalCache = null, baseLine = null) {
345
+ const rootLists = []
346
+ const stack = []
347
+ let idx = startIndex
348
+ let baseIndent = null
349
+
350
+ while (idx < lines.length) {
351
+ const rawLine = lines[idx]
352
+ if (rawLine !== undefined && rawLine.trim().length === 0) {
353
+ if (stack.length > 0) {
354
+ stack[stack.length - 1].list.isLoose = true
355
+ if (baseLine !== null) {
356
+ stack[stack.length - 1].list.lastLine = baseLine + idx
357
+ }
358
+ }
359
+ idx++
360
+ continue
361
+ }
362
+
363
+ const literalInfo = getLiteralInfo(lines, idx, literalCache)
364
+ if (!literalInfo) {
365
+ break
366
+ }
367
+ if (baseIndent === null) {
368
+ baseIndent = literalInfo.indent
369
+ }
370
+ if (literalInfo.indent < baseIndent) {
371
+ break
372
+ }
373
+
374
+ while (stack.length > 0 && literalInfo.indent < stack[stack.length - 1].indent) {
375
+ stack.pop()
376
+ }
377
+
378
+ if (stack.length === 0 || literalInfo.indent > stack[stack.length - 1].indent) {
379
+ const newList = createListNode(literalInfo, baseIndent === null || baseLine === null ? null : baseLine + idx)
380
+ if (stack.length === 0) {
381
+ rootLists.push(newList)
382
+ } else {
383
+ const parent = stack[stack.length - 1].list
384
+ const parentItems = parent.items
385
+ const parentItem = parentItems[parentItems.length - 1]
386
+ if (parentItem) {
387
+ parentItem.children.push(newList)
388
+ } else {
389
+ rootLists.push(newList)
390
+ }
391
+ }
392
+ stack.push({ indent: literalInfo.indent, list: newList })
393
+ }
394
+
395
+ const currentList = stack[stack.length - 1]?.list
396
+ if (!currentList) {
397
+ break
398
+ }
399
+
400
+ currentList.items.push({
401
+ markerInfo: literalInfo.markerInfo,
402
+ content: literalInfo.content,
403
+ children: []
404
+ })
405
+ if (baseLine !== null) {
406
+ currentList.lastLine = baseLine + idx
407
+ }
408
+ idx++
409
+ }
410
+
411
+ return { lists: rootLists, nextIndex: idx }
412
+ }
413
+
414
+ function getLiteralInfo(lines, index, cache = null) {
415
+ if (!cache) {
416
+ return detectLiteralLine(lines[index])
417
+ }
418
+ if (cache[index] === undefined) {
419
+ cache[index] = detectLiteralLine(lines[index]) || null
420
+ }
421
+ return cache[index]
422
+ }
423
+
424
+ function createListNode(literalInfo, lineNumber = null) {
425
+ const markerInfo = literalInfo.markerInfo || {}
426
+ return {
427
+ markerType: markerInfo.type || 'decimal',
428
+ suffix: markerInfo.suffix || '.',
429
+ prefix: markerInfo.prefix || '',
430
+ startNumber: markerInfo.number || 1,
431
+ items: [],
432
+ isLoose: false,
433
+ startLine: lineNumber,
434
+ lastLine: lineNumber
435
+ }
436
+ }
437
+
438
+ function buildReplacementTokens(segments, listItemLevel, TokenClass, paragraphOpen, inlineToken, paragraphClose) {
439
+ const tokens = []
440
+ const literalListPositions = []
441
+ let templateUsed = false
442
+
443
+ for (const segment of segments) {
444
+ if (segment.type === 'text') {
445
+ if (!segment.text) {
446
+ templateUsed = true
447
+ continue
448
+ }
449
+ const template = !templateUsed ? { open: paragraphOpen, inline: inlineToken, close: paragraphClose } : null
450
+ tokens.push(...createParagraphTokens(segment.text, listItemLevel, TokenClass, template, segment.tight))
451
+ templateUsed = true
452
+ } else if (segment.type === 'literal') {
453
+ for (const listNode of segment.lists) {
454
+ const relativeIndex = tokens.length
455
+ tokens.push(...buildListTokens(listNode, listItemLevel + 1, TokenClass))
456
+ literalListPositions.push({ relativeIndex, level: listItemLevel + 1 })
457
+ }
458
+ templateUsed = true
459
+ }
460
+ }
461
+
462
+ return { tokens, literalListPositions }
463
+ }
464
+
465
+ function createParagraphTokens(text, listItemLevel, TokenClass, template, forceTight = false) {
466
+ if (!text) {
467
+ return []
468
+ }
469
+ const tokens = []
470
+ const level = listItemLevel + 1
471
+ const open = template ? cloneToken(template.open) : new TokenClass('paragraph_open', 'p', 1)
472
+ open.level = level
473
+ open.block = true
474
+ const baseHidden = template && typeof template.open?.hidden === 'boolean' ? template.open.hidden : false
475
+ const shouldHide = baseHidden
476
+ open.hidden = shouldHide
477
+ if (forceTight) {
478
+ open._literalTight = true
479
+ }
480
+ tokens.push(open)
481
+
482
+ const inline = new TokenClass('inline', '', 0)
483
+ inline.level = level + 1
484
+ inline.content = text
485
+ inline.children = []
486
+ if (template?.inline?.meta) {
487
+ inline.meta = { ...template.inline.meta }
488
+ }
489
+ if (template?.inline?.attrs) {
490
+ inline.attrs = template.inline.attrs.map(([name, value]) => [name, value])
491
+ }
492
+ inline.block = template?.inline?.block ?? false
493
+ inline.hidden = template?.inline?.hidden ?? false
494
+ tokens.push(inline)
495
+
496
+ const close = template ? cloneToken(template.close) : new TokenClass('paragraph_close', 'p', -1)
497
+ close.level = level
498
+ close.block = true
499
+ close.hidden = shouldHide
500
+ if (forceTight) {
501
+ close._literalTight = true
502
+ }
503
+ tokens.push(close)
504
+ return tokens
505
+ }
506
+
507
+ function buildListTokens(listNode, listLevel, TokenClass) {
508
+ const tokens = []
509
+ const listOpen = new TokenClass('ordered_list_open', 'ol', 1)
510
+ listOpen.level = listLevel
511
+ listOpen.block = true
512
+ listOpen.markup = listNode.suffix || '.'
513
+ listOpen.attrs = null
514
+ listOpen._literalList = true
515
+ if (typeof listNode.startLine === 'number') {
516
+ listOpen._literalStartLine = listNode.startLine
517
+ }
518
+ if (typeof listNode.lastLine === 'number') {
519
+ listOpen._literalLastLine = listNode.lastLine
520
+ }
521
+ if (typeof listNode.startNumber === 'number' && listNode.startNumber !== 1) {
522
+ listOpen.attrs = [['start', String(listNode.startNumber)]]
523
+ }
524
+ tokens.push(listOpen)
525
+
526
+ const listIsLoose = !!listNode.isLoose
527
+ for (const item of listNode.items) {
528
+ tokens.push(...buildListItemTokens(item, listLevel, TokenClass, listIsLoose))
529
+ }
530
+
531
+ const listClose = new TokenClass('ordered_list_close', 'ol', -1)
532
+ listClose.level = listLevel
533
+ listClose.block = true
534
+ listClose.markup = listNode.suffix || '.'
535
+ tokens.push(listClose)
536
+ return tokens
537
+ }
538
+
539
+ function buildListItemTokens(item, listLevel, TokenClass, parentListIsLoose = false) {
540
+ const tokens = []
541
+ const itemLevel = listLevel + 1
542
+ const liOpen = new TokenClass('list_item_open', 'li', 1)
543
+ liOpen.level = itemLevel
544
+ liOpen.block = true
545
+ liOpen.markup = item.markerInfo?.suffix || '.'
546
+ liOpen.info = item.markerInfo?.number !== undefined ? String(item.markerInfo.number) : ''
547
+ tokens.push(liOpen)
548
+
549
+ const paragraphLevel = itemLevel + 1
550
+ const pOpen = new TokenClass('paragraph_open', 'p', 1)
551
+ pOpen.level = paragraphLevel
552
+ pOpen.block = true
553
+ pOpen.hidden = !parentListIsLoose
554
+ if (!parentListIsLoose) {
555
+ pOpen._literalTight = true
556
+ }
557
+ tokens.push(pOpen)
558
+
559
+ const inline = new TokenClass('inline', '', 0)
560
+ inline.level = paragraphLevel + 1
561
+ inline.content = item.content || ''
562
+ inline.children = []
563
+ tokens.push(inline)
564
+
565
+ const pClose = new TokenClass('paragraph_close', 'p', -1)
566
+ pClose.level = paragraphLevel
567
+ pClose.block = true
568
+ pClose.hidden = !parentListIsLoose
569
+ if (!parentListIsLoose) {
570
+ pClose._literalTight = true
571
+ }
572
+ tokens.push(pClose)
573
+
574
+ if (item.children && item.children.length > 0) {
575
+ for (const childList of item.children) {
576
+ tokens.push(...buildListTokens(childList, itemLevel + 1, TokenClass))
577
+ }
578
+ }
579
+
580
+ const liClose = new TokenClass('list_item_close', 'li', -1)
581
+ liClose.level = itemLevel
582
+ liClose.block = true
583
+ tokens.push(liClose)
584
+ return tokens
585
+ }
586
+
587
+ function cloneToken(token) {
588
+ if (!token) {
589
+ return token
590
+ }
591
+ const TokenClass = token.constructor
592
+ const cloned = new TokenClass(token.type, token.tag, token.nesting)
593
+ cloned.attrs = token.attrs ? token.attrs.map(([name, value]) => [name, value]) : null
594
+ cloned.map = token.map ? [...token.map] : null
595
+ cloned.level = token.level
596
+ cloned.content = token.content
597
+ cloned.markup = token.markup
598
+ cloned.info = token.info
599
+ cloned.meta = token.meta ? { ...token.meta } : null
600
+ cloned.block = token.block
601
+ cloned.hidden = token.hidden
602
+ cloned.children = token.children ? token.children.map(child => cloneToken(child)) : null
603
+ return cloned
604
+ }
605
+
606
+ function revealFirstParagraph(tokens, listItemOpenIdx, listItemCloseIdx) {
607
+ const paragraphLevel = (tokens[listItemOpenIdx].level ?? 0) + 1
608
+ for (let k = listItemOpenIdx + 1; k < listItemCloseIdx; k++) {
609
+ if (tokens[k].type === 'paragraph_open' && tokens[k].level === paragraphLevel) {
610
+ tokens[k].hidden = false
611
+ for (let m = k + 1; m < listItemCloseIdx; m++) {
612
+ if (tokens[m].type === 'paragraph_close' && tokens[m].level === paragraphLevel) {
613
+ tokens[m].hidden = false
614
+ break
615
+ }
616
+ }
617
+ break
618
+ }
619
+ }
620
+ }
621
+
622
+ function mergeFollowingLists(tokens, listOpenIndex) {
623
+ if (listOpenIndex < 0 || listOpenIndex >= tokens.length) {
624
+ return 0
625
+ }
626
+ const listOpen = tokens[listOpenIndex]
627
+ if (!listOpen || listOpen.type !== 'ordered_list_open') {
628
+ return 0
629
+ }
630
+
631
+ let totalDelta = 0
632
+ let listCloseIndex = findMatchingClose(tokens, listOpenIndex, 'ordered_list_open', 'ordered_list_close')
633
+ if (listCloseIndex === -1) {
634
+ return 0
635
+ }
636
+
637
+ let forceLoose = false
638
+ while (listCloseIndex + 1 < tokens.length) {
639
+ const nextToken = tokens[listCloseIndex + 1]
640
+ if (!nextToken ||
641
+ nextToken.type !== 'ordered_list_open' ||
642
+ nextToken.level !== listOpen.level) {
643
+ break
644
+ }
645
+ const nextClose = findMatchingClose(tokens, listCloseIndex + 1, 'ordered_list_open', 'ordered_list_close')
646
+ if (nextClose === -1) {
647
+ break
648
+ }
649
+ const nextIsLiteral = !!nextToken._literalList
650
+ const innerTokens = tokens.slice(listCloseIndex + 2, nextClose)
651
+ const removeCount = nextClose - (listCloseIndex + 1) + 1
652
+ tokens.splice(listCloseIndex + 1, removeCount)
653
+ tokens.splice(listCloseIndex, 0, ...innerTokens)
654
+
655
+ listCloseIndex = listCloseIndex + innerTokens.length
656
+ totalDelta -= 2
657
+ if (!nextIsLiteral) {
658
+ const startLine = Array.isArray(nextToken.map) ? nextToken.map[0] : null
659
+ if (typeof startLine === 'number' && typeof listOpen._literalLastLine === 'number') {
660
+ if (startLine - listOpen._literalLastLine > 1) {
661
+ forceLoose = true
662
+ }
663
+ }
664
+ }
665
+ if (typeof nextToken._literalLastLine === 'number') {
666
+ listOpen._literalLastLine = nextToken._literalLastLine
667
+ } else if (Array.isArray(nextToken.map) && typeof nextToken.map[1] === 'number') {
668
+ listOpen._literalLastLine = nextToken.map[1]
669
+ }
670
+ }
671
+
672
+ if (forceLoose) {
673
+ markLiteralListLoose(tokens, listOpenIndex)
674
+ }
675
+ return totalDelta
676
+ }
677
+
678
+ function markLiteralListLoose(tokens, listOpenIndex) {
679
+ const listOpen = tokens[listOpenIndex]
680
+ if (!listOpen || listOpen.type !== 'ordered_list_open') {
681
+ return
682
+ }
683
+ const listCloseIndex = findMatchingClose(tokens, listOpenIndex, 'ordered_list_open', 'ordered_list_close')
684
+ if (listCloseIndex === -1) {
685
+ return
686
+ }
687
+ const paragraphLevel = (listOpen.level ?? 0) + 2
688
+ for (let i = listOpenIndex + 1; i < listCloseIndex; i++) {
689
+ const token = tokens[i]
690
+ if ((token.type === 'paragraph_open' || token.type === 'paragraph_close') &&
691
+ token.level === paragraphLevel) {
692
+ token.hidden = false
693
+ if (token._literalTight) {
694
+ delete token._literalTight
695
+ }
696
+ }
697
+ }
698
+ }
699
+
700
+ function splitOrderedListForLiteralChildren(tokens, listOpenIndex, TokenClass) {
701
+ const listToken = tokens[listOpenIndex]
702
+ const listCloseIndex = findMatchingClose(tokens, listOpenIndex, 'ordered_list_open', 'ordered_list_close')
703
+ if (listCloseIndex === -1) {
704
+ return 0
705
+ }
706
+ const childLevel = (listToken.level ?? 0) + 1
707
+ const ranges = []
708
+
709
+ for (let idx = listOpenIndex + 1; idx < listCloseIndex; idx++) {
710
+ const token = tokens[idx]
711
+ if (token.type === 'list_item_open' && token.level === childLevel) {
712
+ const closeIdx = findMatchingClose(tokens, idx, 'list_item_open', 'list_item_close')
713
+ if (closeIdx === -1) {
714
+ break
715
+ }
716
+ ranges.push({ open: idx, close: closeIdx })
717
+ idx = closeIdx
718
+ }
719
+ }
720
+
721
+ if (ranges.length <= 1) {
722
+ return 0
723
+ }
724
+
725
+ const extraRanges = ranges.slice(1)
726
+ const childTokens = []
727
+ let removedCount = 0
728
+
729
+ for (const range of extraRanges) {
730
+ childTokens.push(...tokens.slice(range.open, range.close + 1))
731
+ }
732
+
733
+ for (let k = extraRanges.length - 1; k >= 0; k--) {
734
+ const range = extraRanges[k]
735
+ const len = range.close - range.open + 1
736
+ tokens.splice(range.open, len)
737
+ removedCount += len
738
+ }
739
+
740
+ if (listToken._markerInfo) {
741
+ delete listToken._markerInfo
742
+ }
743
+
744
+ const levelShift = 2
745
+ for (const token of childTokens) {
746
+ if (typeof token.level === 'number') {
747
+ token.level += levelShift
748
+ }
749
+ }
750
+
751
+ const nestedListOpen = new TokenClass('ordered_list_open', 'ol', 1)
752
+ nestedListOpen.level = (listToken.level ?? 0) + 2
753
+ nestedListOpen.block = true
754
+ nestedListOpen.markup = listToken.markup
755
+ nestedListOpen.attrs = null
756
+ nestedListOpen._literalList = true
757
+
758
+ const firstChild = childTokens.find(t => t.type === 'list_item_open')
759
+ if (firstChild?.info) {
760
+ const num = parseInt(firstChild.info, 10)
761
+ if (!Number.isNaN(num) && num !== 1) {
762
+ nestedListOpen.attrs = [['start', String(num)]]
763
+ }
764
+ }
765
+
766
+ const nestedListClose = new TokenClass('ordered_list_close', 'ol', -1)
767
+ nestedListClose.level = nestedListOpen.level
768
+ nestedListClose.block = true
769
+ nestedListClose.markup = listToken.markup
770
+
771
+ const insertionIndex = ranges[0].close
772
+ tokens.splice(insertionIndex, 0, nestedListOpen, ...childTokens, nestedListClose)
773
+
774
+ const addedCount = childTokens.length + 2
775
+ return addedCount - removedCount
776
+ }