@peaceroad/markdown-it-strong-ja 0.8.0 → 0.9.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/src/token-core.js CHANGED
@@ -10,13 +10,15 @@ import {
10
10
  MODE_FLAG_COMPATIBLE,
11
11
  MODE_FLAG_AGGRESSIVE,
12
12
  MODE_FLAG_JAPANESE_PLUS,
13
- getRuntimeOpt
13
+ getRuntimeOpt,
14
+ hasRuntimeOverride
14
15
  } from './token-utils.js'
15
16
 
16
17
  const SCAN_DELIMS_PATCHED = Symbol.for('strongJaTokenScanDelimsPatched')
17
18
  const SINGLE_STAR_LOOKAROUND_MAX = 16
18
19
  const PREV_STAR_HAS_OPENER = 1
19
20
  const PREV_STAR_HAS_JP_BETWEEN = 2
21
+ const SCAN_DELIMS_LOOKUP_KEY = Symbol.for('strongJaTokenScanDelimsLookup')
20
22
 
21
23
  const isSoftSpaceCode = (code) => {
22
24
  return code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_IDEOGRAPHIC_SPACE
@@ -194,6 +196,28 @@ const rebuildInlineLevels = (tokens) => {
194
196
  }
195
197
  }
196
198
 
199
+ const rebuildInlineLevelsFrom = (tokens, startIdx = 0) => {
200
+ if (!tokens || tokens.length === 0) return
201
+ let from = startIdx > 0 ? startIdx : 0
202
+ if (from >= tokens.length) return
203
+ let level = 0
204
+ if (from > 0) {
205
+ const prev = tokens[from - 1]
206
+ if (prev) {
207
+ level = prev.level
208
+ if (prev.nesting === 1) level++
209
+ else if (prev.nesting === -1) level--
210
+ }
211
+ }
212
+ for (let i = from; i < tokens.length; i++) {
213
+ const t = tokens[i]
214
+ if (!t) continue
215
+ t.level = level
216
+ if (t.nesting === 1) level++
217
+ else if (t.nesting === -1) level--
218
+ }
219
+ }
220
+
197
221
  const findLinkOpen = (tokens, closeIdx) => {
198
222
  let depth = 0
199
223
  for (let i = closeIdx; i >= 0; i--) {
@@ -258,7 +282,58 @@ const isAsciiGuardCloseWrapper = (code) => {
258
282
  code === 0x60 // `
259
283
  }
260
284
 
261
- const findPrevNonSpaceIndex = (src, start) => {
285
+ const buildScanDelimsLookupCache = (src) => {
286
+ const len = typeof src === 'string' ? src.length : 0
287
+ const prevNonSpaceSameLine = new Int32Array(len)
288
+ const nextNonSpaceSameLine = new Int32Array(len)
289
+ prevNonSpaceSameLine.fill(-1)
290
+ nextNonSpaceSameLine.fill(-1)
291
+
292
+ let prev = -1
293
+ for (let i = 0; i < len; i++) {
294
+ const code = src.charCodeAt(i)
295
+ if (code === CHAR_NEWLINE) {
296
+ prev = -1
297
+ continue
298
+ }
299
+ if (!isSoftSpaceCode(code)) prev = i
300
+ prevNonSpaceSameLine[i] = prev
301
+ }
302
+
303
+ let next = -1
304
+ for (let i = len - 1; i >= 0; i--) {
305
+ const code = src.charCodeAt(i)
306
+ if (code === CHAR_NEWLINE) {
307
+ next = -1
308
+ continue
309
+ }
310
+ if (!isSoftSpaceCode(code)) next = i
311
+ nextNonSpaceSameLine[i] = next
312
+ }
313
+
314
+ return {
315
+ src,
316
+ prevNonSpaceSameLine,
317
+ nextNonSpaceSameLine
318
+ }
319
+ }
320
+
321
+ const getScanDelimsLookupCache = (state) => {
322
+ if (!state || typeof state.src !== 'string') return null
323
+ const cached = state[SCAN_DELIMS_LOOKUP_KEY]
324
+ if (cached && cached.src === state.src) return cached
325
+ const next = buildScanDelimsLookupCache(state.src)
326
+ state[SCAN_DELIMS_LOOKUP_KEY] = next
327
+ return next
328
+ }
329
+
330
+ const findPrevNonSpaceIndex = (src, start, lookupCache = null) => {
331
+ if (start < 0) return -1
332
+ if (lookupCache &&
333
+ lookupCache.src === src &&
334
+ start < lookupCache.prevNonSpaceSameLine.length) {
335
+ return lookupCache.prevNonSpaceSameLine[start]
336
+ }
262
337
  for (let i = start; i >= 0; i--) {
263
338
  const code = src.charCodeAt(i)
264
339
  if (code === CHAR_NEWLINE) return -1
@@ -268,7 +343,14 @@ const findPrevNonSpaceIndex = (src, start) => {
268
343
  return -1
269
344
  }
270
345
 
271
- const findNextNonSpaceIndex = (src, start, max) => {
346
+ const findNextNonSpaceIndex = (src, start, max, lookupCache = null) => {
347
+ if (lookupCache &&
348
+ lookupCache.src === src &&
349
+ start >= 0 &&
350
+ start < lookupCache.nextNonSpaceSameLine.length) {
351
+ const next = lookupCache.nextNonSpaceSameLine[start]
352
+ return next !== -1 && next < max ? next : -1
353
+ }
272
354
  for (let i = start; i < max; i++) {
273
355
  const code = src.charCodeAt(i)
274
356
  if (code === CHAR_NEWLINE) return -1
@@ -278,26 +360,26 @@ const findNextNonSpaceIndex = (src, start, max) => {
278
360
  return -1
279
361
  }
280
362
 
281
- const hasAsciiStartAfterOptionalOpenWrappers = (src, index, max) => {
363
+ const hasAsciiStartAfterOptionalOpenWrappers = (src, index, max, lookupCache = null) => {
282
364
  let i = index
283
365
  // Two wrappers are enough for common shapes: * [ "word" ]*
284
366
  for (let wrappers = 0; wrappers < 2 && i >= 0 && i < max; wrappers++) {
285
367
  const code = src.charCodeAt(i)
286
368
  if (!isAsciiGuardOpenWrapper(code)) break
287
- i = findNextNonSpaceIndex(src, i + 1, max)
369
+ i = findNextNonSpaceIndex(src, i + 1, max, lookupCache)
288
370
  if (i === -1) return false
289
371
  }
290
372
  if (i < 0 || i >= max) return false
291
373
  return isAsciiAlphaNum(src.charCodeAt(i))
292
374
  }
293
375
 
294
- const hasAsciiEndBeforeOptionalCloseWrappers = (src, index) => {
376
+ const hasAsciiEndBeforeOptionalCloseWrappers = (src, index, lookupCache = null) => {
295
377
  let i = index
296
378
  // Two wrappers are enough for common shapes: *["word"] *
297
379
  for (let wrappers = 0; wrappers < 2 && i >= 0; wrappers++) {
298
380
  const code = src.charCodeAt(i)
299
381
  if (!isAsciiGuardCloseWrapper(code)) break
300
- i = findPrevNonSpaceIndex(src, i - 1)
382
+ i = findPrevNonSpaceIndex(src, i - 1, lookupCache)
301
383
  if (i === -1) return false
302
384
  }
303
385
  if (i < 0) return false
@@ -331,7 +413,17 @@ const isSentenceBoundaryStop = (code) => {
331
413
  code === 0x2049 // ⁉
332
414
  }
333
415
 
334
- const findPrevNonSpaceLimited = (src, start, maxLook) => {
416
+ const findPrevNonSpaceLimited = (src, start, maxLook, lookupCache = null) => {
417
+ if (lookupCache &&
418
+ lookupCache.src === src &&
419
+ start >= 0 &&
420
+ start < lookupCache.prevNonSpaceSameLine.length) {
421
+ const prev = lookupCache.prevNonSpaceSameLine[start]
422
+ if (prev !== -1 && (start - prev) < maxLook) {
423
+ return src.charCodeAt(prev)
424
+ }
425
+ return 0
426
+ }
335
427
  let looked = 0
336
428
  for (let i = start; i >= 0; i--) {
337
429
  if (looked >= maxLook) break
@@ -344,7 +436,17 @@ const findPrevNonSpaceLimited = (src, start, maxLook) => {
344
436
  return 0
345
437
  }
346
438
 
347
- const findNextNonSpaceLimited = (src, start, max, maxLook) => {
439
+ const findNextNonSpaceLimited = (src, start, max, maxLook, lookupCache = null) => {
440
+ if (lookupCache &&
441
+ lookupCache.src === src &&
442
+ start >= 0 &&
443
+ start < lookupCache.nextNonSpaceSameLine.length) {
444
+ const next = lookupCache.nextNonSpaceSameLine[start]
445
+ if (next !== -1 && next < max && (next - start) < maxLook) {
446
+ return src.charCodeAt(next)
447
+ }
448
+ return 0
449
+ }
348
450
  let looked = 0
349
451
  for (let i = start; i < max; i++) {
350
452
  if (looked >= maxLook) break
@@ -357,13 +459,13 @@ const findNextNonSpaceLimited = (src, start, max, maxLook) => {
357
459
  return 0
358
460
  }
359
461
 
360
- const hasJapaneseContextForBracketWrapper = (src, start, pos, max, lastChar, nextChar) => {
462
+ const hasJapaneseContextForBracketWrapper = (src, start, pos, max, lastChar, nextChar, lookupCache = null) => {
361
463
  if (isWrapperOpenLike(nextChar)) {
362
- const right = findNextNonSpaceLimited(src, pos, max, SINGLE_STAR_LOOKAROUND_MAX)
464
+ const right = findNextNonSpaceLimited(src, pos, max, SINGLE_STAR_LOOKAROUND_MAX, lookupCache)
363
465
  if (isJapaneseChar(right)) return true
364
466
  }
365
467
  if (isWrapperCloseLike(lastChar)) {
366
- const left = findPrevNonSpaceLimited(src, start - 2, SINGLE_STAR_LOOKAROUND_MAX)
468
+ const left = findPrevNonSpaceLimited(src, start - 2, SINGLE_STAR_LOOKAROUND_MAX, lookupCache)
367
469
  if (isJapaneseChar(left)) return true
368
470
  }
369
471
  return false
@@ -393,10 +495,11 @@ const scanPrevSingleStarContextFlags = (src, start) => {
393
495
  }
394
496
 
395
497
  const ensurePrevStarFlags = (src, start, prevStarFlags) => {
396
- return prevStarFlags >= 0 ? prevStarFlags : scanPrevSingleStarContextFlags(src, start)
498
+ if (prevStarFlags >= 0) return prevStarFlags
499
+ return scanPrevSingleStarContextFlags(src, start)
397
500
  }
398
501
 
399
- const fixTrailingStrong = (tokens) => {
502
+ const fixTrailingStrong = (tokens, onChangeStart = null) => {
400
503
  let changed = false
401
504
  for (let i = 1; i < tokens.length; i++) {
402
505
  const token = tokens[i]
@@ -435,21 +538,23 @@ const fixTrailingStrong = (tokens) => {
435
538
  tokens[innerOpenIdx + 2] && tokens[innerOpenIdx + 2].type === 'em_close' &&
436
539
  tokens[innerOpenIdx + 3] && tokens[innerOpenIdx + 3].type === 'text' &&
437
540
  closeIdx === innerOpenIdx + 4) {
438
- tokens.splice(innerOpenIdx + 2, 1)
439
- tokens.splice(innerOpenIdx, 1)
440
541
  const movedOpen = new Token('em_open', 'em', 1)
441
542
  movedOpen.markup = '*'
442
543
  const movedClose = new Token('em_close', 'em', -1)
443
544
  movedClose.markup = '*'
444
- tokens.splice(innerOpenIdx + 1, 0, movedOpen)
445
- tokens.splice(innerOpenIdx + 3, 0, movedClose)
545
+ const innerReplacement = [
546
+ tokens[innerOpenIdx + 1],
547
+ movedOpen,
548
+ tokens[innerOpenIdx + 3],
549
+ movedClose
550
+ ]
551
+ tokens.splice(innerOpenIdx, 4, ...innerReplacement)
446
552
  }
447
553
 
448
554
  const before = token.content.slice(0, starIdx)
449
555
  const after = token.content.slice(starIdx + 2)
450
556
 
451
- tokens.splice(closeIdx, 1)
452
- if (closeIdx < i) i--
557
+ if (onChangeStart) onChangeStart(openIdx)
453
558
 
454
559
  const openToken = tokens[openIdx]
455
560
  if (!openToken) continue
@@ -458,28 +563,27 @@ const fixTrailingStrong = (tokens) => {
458
563
  openToken.markup = '**'
459
564
  openToken.nesting = 1
460
565
 
566
+ const strongClose = new Token('strong_close', 'strong', -1)
567
+ strongClose.markup = '**'
568
+ const trailingReplacement = []
461
569
  if (before) {
462
570
  token.content = before
463
- } else {
464
- tokens.splice(i, 1)
465
- i--
571
+ trailingReplacement.push(token)
466
572
  }
467
-
468
- const insertAt = i + 1
469
- const strongClose = new Token('strong_close', 'strong', -1)
470
- strongClose.markup = '**'
471
- tokens.splice(insertAt, 0, strongClose)
573
+ trailingReplacement.push(strongClose)
472
574
  if (after) {
473
575
  const tail = new Token('text', '', 0)
474
576
  tail.content = after
475
- tokens.splice(insertAt + 1, 0, tail)
577
+ trailingReplacement.push(tail)
476
578
  }
579
+ tokens.splice(closeIdx, 2, ...trailingReplacement)
580
+ i = closeIdx + trailingReplacement.length - 1
477
581
  changed = true
478
582
  }
479
583
  return changed
480
584
  }
481
585
 
482
- function fixEmOuterStrongSequence(tokens) {
586
+ function fixEmOuterStrongSequence(tokens, onChangeStart = null) {
483
587
  let changed = false
484
588
  let i = 0
485
589
  while (i < tokens.length) {
@@ -536,6 +640,7 @@ function fixEmOuterStrongSequence(tokens) {
536
640
  continue
537
641
  }
538
642
 
643
+ if (onChangeStart) onChangeStart(idx0)
539
644
  t0.type = 'strong_open'
540
645
  t0.tag = 'strong'
541
646
  t0.markup = '**'
@@ -548,28 +653,23 @@ function fixEmOuterStrongSequence(tokens) {
548
653
  const strongClose = new Token('strong_close', 'strong', -1)
549
654
  strongClose.markup = '**'
550
655
 
551
- tokens.splice(idx7, 1)
552
- tokens.splice(idx5, 1)
553
- tokens.splice(idx3, 1)
554
- tokens.splice(idx1, 1)
555
-
556
- const idx4AfterRemove = idx4 - 2
557
- const idx6AfterRemove = idx6 - 3
558
- tokens.splice(idx4AfterRemove, 0, emOpen)
559
-
560
- const idx6BeforeEmClose = idx6AfterRemove + 1
561
- tokens.splice(idx6BeforeEmClose, 0, emClose)
562
-
563
- const idx6AfterEmClose = idx6BeforeEmClose + 1
564
- tokens.splice(idx6AfterEmClose + 1, 0, strongClose)
656
+ const replacement = [
657
+ ...tokens.slice(idx1 + 1, idx3),
658
+ emOpen,
659
+ ...tokens.slice(idx3 + 1, idx5),
660
+ emClose,
661
+ ...tokens.slice(idx5 + 1, idx7),
662
+ strongClose
663
+ ]
664
+ tokens.splice(idx1, idx7 - idx1 + 1, ...replacement)
565
665
 
566
666
  changed = true
567
- i = idx6AfterEmClose + 2
667
+ i = idx1 + replacement.length
568
668
  }
569
669
  return changed
570
670
  }
571
671
 
572
- const shiftEmWithLeadingStar = (tokens, rangeStart, rangeEnd, closeIdx) => {
672
+ const shiftEmWithLeadingStar = (tokens, rangeStart, rangeEnd, closeIdx, onChangeStart = null) => {
573
673
  let openIdx = findMatchingEmOpen(tokens, closeIdx)
574
674
  if (openIdx === -1 || openIdx < rangeStart || openIdx >= rangeEnd) return false
575
675
 
@@ -598,33 +698,26 @@ const shiftEmWithLeadingStar = (tokens, rangeStart, rangeEnd, closeIdx) => {
598
698
  const starToken = tokens[starTokenIdx]
599
699
  const before = starToken.content.slice(0, starPos)
600
700
  const after = starToken.content.slice(starPos + 1)
601
- let insertAt = starTokenIdx
602
- if (before) {
603
- starToken.content = before
604
- insertAt = starTokenIdx + 1
605
- } else {
606
- tokens.splice(starTokenIdx, 1)
607
- if (starTokenIdx < openIdx) {
608
- openIdx--
609
- closeIdx--
610
- }
611
- }
701
+ if (onChangeStart) onChangeStart(starTokenIdx)
612
702
 
613
703
  const emOpen = new Token('em_open', 'em', 1)
614
704
  emOpen.markup = '*'
615
- tokens.splice(insertAt, 0, emOpen)
616
- if (insertAt <= openIdx) {
617
- openIdx++
618
- closeIdx++
705
+ const prefixReplacement = []
706
+ if (before) {
707
+ starToken.content = before
708
+ prefixReplacement.push(starToken)
619
709
  }
710
+ prefixReplacement.push(emOpen)
620
711
  if (after) {
621
712
  const afterToken = new Token('text', '', 0)
622
713
  afterToken.content = after
623
- tokens.splice(insertAt + 1, 0, afterToken)
624
- if (insertAt + 1 <= openIdx) {
625
- openIdx++
626
- closeIdx++
627
- }
714
+ prefixReplacement.push(afterToken)
715
+ }
716
+ const prefixDelta = prefixReplacement.length - 1
717
+ tokens.splice(starTokenIdx, 1, ...prefixReplacement)
718
+ if (starTokenIdx < openIdx) {
719
+ openIdx += prefixDelta
720
+ closeIdx += prefixDelta
628
721
  }
629
722
 
630
723
  const openToken = tokens[openIdx]
@@ -634,19 +727,20 @@ const shiftEmWithLeadingStar = (tokens, rangeStart, rangeEnd, closeIdx) => {
634
727
  openToken.markup = '*'
635
728
  openToken.nesting = -1
636
729
 
637
- tokens.splice(closeIdx, 1)
730
+ const tailReplacement = []
638
731
  const tailIdx = closeIdx - 1
639
732
  if (tailIdx >= 0 && tokens[tailIdx] && tokens[tailIdx].type === 'text') {
640
733
  tokens[tailIdx].content += '*'
641
734
  } else {
642
735
  const tail = new Token('text', '', 0)
643
736
  tail.content = '*'
644
- tokens.splice(closeIdx, 0, tail)
737
+ tailReplacement.push(tail)
645
738
  }
739
+ tokens.splice(closeIdx, 1, ...tailReplacement)
646
740
  return true
647
741
  }
648
742
 
649
- const fixLeadingAsteriskEm = (tokens) => {
743
+ const fixLeadingAsteriskEm = (tokens, onChangeStart = null) => {
650
744
  let changed = false
651
745
  for (let i = 0; i < tokens.length; i++) {
652
746
  const t = tokens[i]
@@ -656,7 +750,7 @@ const fixLeadingAsteriskEm = (tokens) => {
656
750
  const linkCloseIdx = nextIdx
657
751
  const linkOpenIdx = findLinkOpen(tokens, linkCloseIdx)
658
752
  if (linkOpenIdx === -1) continue
659
- if (shiftEmWithLeadingStar(tokens, linkOpenIdx + 1, linkCloseIdx, i)) {
753
+ if (shiftEmWithLeadingStar(tokens, linkOpenIdx + 1, linkCloseIdx, i, onChangeStart)) {
660
754
  changed = true
661
755
  i = linkCloseIdx
662
756
  }
@@ -683,7 +777,7 @@ const patchScanDelims = (md) => {
683
777
 
684
778
  const baseOpt = this.md ? this.md.__strongJaTokenOpt : null
685
779
  const overrideOpt = this.env && this.env.__strongJaTokenOpt
686
- const opt = overrideOpt ? getRuntimeOpt(this, baseOpt) : baseOpt
780
+ const opt = hasRuntimeOverride(overrideOpt) ? getRuntimeOpt(this, baseOpt) : baseOpt
687
781
  if (!opt) {
688
782
  return base
689
783
  }
@@ -694,6 +788,7 @@ const patchScanDelims = (md) => {
694
788
  const plusMode = (modeFlags & MODE_FLAG_JAPANESE_PLUS) !== 0
695
789
  const aggressiveMode = (modeFlags & MODE_FLAG_AGGRESSIVE) !== 0
696
790
  const max = this.posMax
791
+ let lookupCache = null
697
792
  const lastChar = start > 0 ? src.charCodeAt(start - 1) : 0x20
698
793
 
699
794
  const count = base && base.length ? base.length : 1
@@ -706,7 +801,15 @@ const patchScanDelims = (md) => {
706
801
  const rightJapanese = isJapaneseChar(nextChar)
707
802
  let hasJapaneseContext = leftJapanese || rightJapanese
708
803
  if (!hasJapaneseContext && count === 1) {
709
- hasJapaneseContext = hasJapaneseContextForBracketWrapper(src, start, pos, max, lastChar, nextChar)
804
+ hasJapaneseContext = hasJapaneseContextForBracketWrapper(
805
+ src,
806
+ start,
807
+ pos,
808
+ max,
809
+ lastChar,
810
+ nextChar,
811
+ lookupCache || (lookupCache = getScanDelimsLookupCache(this))
812
+ )
710
813
  }
711
814
  if (!hasJapaneseContext && count === 1 && isExtraSingleStarClosePunct(lastChar)) {
712
815
  prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
@@ -721,22 +824,31 @@ const patchScanDelims = (md) => {
721
824
  let isLastWhiteSpace = isWhiteSpace(lastChar) || isSoftSpaceCode(lastChar)
722
825
  let isNextWhiteSpace = isWhiteSpace(nextChar) || isSoftSpaceCode(nextChar)
723
826
  if (isLastWhiteSpace && isSoftSpaceCode(lastChar)) {
724
- const prevNonSpaceIdx = findPrevNonSpaceIndex(src, start - 2)
827
+ const prevNonSpaceIdx = findPrevNonSpaceIndex(
828
+ src,
829
+ start - 2,
830
+ lookupCache || (lookupCache = getScanDelimsLookupCache(this))
831
+ )
725
832
  if (prevNonSpaceIdx !== -1) {
726
833
  const prevNonSpaceLocal = src.charCodeAt(prevNonSpaceIdx)
727
834
  const plusStrictAsciiBoundary = plusMode &&
728
- hasAsciiEndBeforeOptionalCloseWrappers(src, prevNonSpaceIdx)
835
+ hasAsciiEndBeforeOptionalCloseWrappers(src, prevNonSpaceIdx, lookupCache)
729
836
  if (prevNonSpaceLocal !== CHAR_ASTERISK && !plusStrictAsciiBoundary) {
730
837
  isLastWhiteSpace = false
731
838
  }
732
839
  }
733
840
  }
734
841
  if (isNextWhiteSpace && isSoftSpaceCode(nextChar)) {
735
- const nextNonSpaceIdx = findNextNonSpaceIndex(src, pos, max)
842
+ const nextNonSpaceIdx = findNextNonSpaceIndex(
843
+ src,
844
+ pos,
845
+ max,
846
+ lookupCache || (lookupCache = getScanDelimsLookupCache(this))
847
+ )
736
848
  if (nextNonSpaceIdx !== -1) {
737
849
  const nextNonSpace = src.charCodeAt(nextNonSpaceIdx)
738
850
  const plusStrictAsciiBoundary = plusMode &&
739
- hasAsciiStartAfterOptionalOpenWrappers(src, nextNonSpaceIdx, max)
851
+ hasAsciiStartAfterOptionalOpenWrappers(src, nextNonSpaceIdx, max, lookupCache)
740
852
  if (nextNonSpace !== CHAR_ASTERISK && !plusStrictAsciiBoundary) {
741
853
  isNextWhiteSpace = false
742
854
  }
@@ -805,6 +917,7 @@ const patchScanDelims = (md) => {
805
917
 
806
918
  export {
807
919
  rebuildInlineLevels,
920
+ rebuildInlineLevelsFrom,
808
921
  findLinkOpen,
809
922
  nextNonEmptyIndex,
810
923
  fixTrailingStrong,