@peaceroad/markdown-it-strong-ja 0.8.1 → 0.9.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.
package/README.md CHANGED
@@ -396,10 +396,10 @@ Supporting visuals:
396
396
 
397
397
  ## Notes
398
398
 
399
- - Use `state.env.__strongJaTokenOpt` to override options per render.
400
- - Overrides are merged with plugin options, but setup-time behavior (such as rule registration/order) cannot be switched at render time and cannot be retrofitted after the first `.use(...)` on the same `MarkdownIt` instance.
401
- - `mode` and `postprocess` are runtime-effective. `mditAttrs`, `patchCorePush`, and `coreRulesBeforePostprocess` are setup-time effective after the first `.use(...)` on a `MarkdownIt` instance.
399
+ - Use `state.env.__strongJaTokenOpt` to override runtime-effective options per render.
400
+ - Repeated `.use(...)` on the same `MarkdownIt` instance is treated as first-install-wins no-op. Create a new `MarkdownIt` instance for a different plugin option set.
401
+ - Runtime-effective override keys are merged with plugin options, but setup-time behavior (such as rule registration/order) cannot be switched at render time and cannot be retrofitted after the first `.use(...)` on the same `MarkdownIt` instance.
402
+ - `mode` and `postprocess` are runtime-effective via initial install or per-render override. `mditAttrs`, `patchCorePush`, and `coreRulesBeforePostprocess` are setup-time effective after the first `.use(...)` on a `MarkdownIt` instance.
402
403
  - This is an ESM plugin (`type: module`) and is tested against `markdown-it` 14.x in Node.js, browser bundlers, and VS Code extension pipelines that use `markdown-it` ESM.
403
404
  - The implementation relies on `markdown-it` internal ESM modules / core rule internals (`lib/token.mjs`, `lib/common/utils.mjs`, `ruler.__rules__`) plus a `scanDelims` prototype patch, so internal `markdown-it` changes may require plugin updates.
404
405
  - `scanDelims` patch is applied once per `MarkdownIt` prototype in the same process.
405
-
package/index.js CHANGED
@@ -19,31 +19,23 @@ const buildNormalizedOption = (md, option) => {
19
19
  return opt
20
20
  }
21
21
 
22
- const writeSharedOption = (target, source) => {
23
- for (const key of Object.keys(target)) {
24
- delete target[key]
25
- }
26
- Object.assign(target, source)
27
- return target
28
- }
29
-
30
22
  const mditStrongJa = (md, option) => {
31
23
  if (option && typeof option.engine === 'string' && option.engine !== 'token') {
32
24
  throw new Error('mditStrongJa: legacy engine was removed; use token (default)')
33
25
  }
26
+ if (md.__strongJaTokenInstalled) {
27
+ return md
28
+ }
34
29
  const nextOpt = buildNormalizedOption(md, option)
35
- const opt = md.__strongJaTokenOpt && typeof md.__strongJaTokenOpt === 'object'
36
- ? writeSharedOption(md.__strongJaTokenOpt, nextOpt)
37
- : nextOpt
38
-
39
- md.__strongJaTokenOpt = opt
30
+ md.__strongJaTokenOpt = nextOpt
40
31
  patchScanDelims(md)
41
- registerTokenCompat(md, opt)
32
+ registerTokenCompat(md, nextOpt)
42
33
 
43
- registerTokenPostprocess(md, opt)
44
- ensureCoreRuleOrder(md, opt.__strongJaNormalizedCoreRulesBeforePostprocess, 'strong_ja_token_postprocess')
34
+ registerTokenPostprocess(md, nextOpt)
35
+ ensureCoreRuleOrder(md, nextOpt.__strongJaNormalizedCoreRulesBeforePostprocess, 'strong_ja_token_postprocess')
36
+ md.__strongJaTokenInstalled = true
45
37
 
46
38
  return md
47
39
  }
48
40
 
49
- export default mditStrongJa
41
+ export default mditStrongJa
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@peaceroad/markdown-it-strong-ja",
3
3
  "description": "Extends asterisk emphasis handling for Japanese text while keeping markdown-it behavior as close as practical.",
4
- "version": "0.8.1",
4
+ "version": "0.9.1",
5
5
  "main": "index.js",
6
6
  "type": "module",
7
7
  "files": [
@@ -26,6 +26,7 @@
26
26
  "test:all": "node test/test-all.js",
27
27
  "bench:scan": "node test/material/perf-scan-delims.mjs",
28
28
  "bench:postprocess": "node test/material/perf-postprocess.mjs",
29
+ "bench:isolated": "node test/material/bench-isolated.mjs",
29
30
  "analyze:postprocess-calls": "node test/material/analyze-postprocess-calls.mjs",
30
31
  "analyze:fastpath": "node test/material/analyze-fastpath-hits.mjs"
31
32
  },
@@ -36,14 +37,13 @@
36
37
  "markdown-it": "^14.1.0"
37
38
  },
38
39
  "devDependencies": {
39
- "@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.8",
40
- "@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.10.0",
40
+ "@peaceroad/markdown-it-cjk-breaks-mod": "^0.1.10",
41
+ "@peaceroad/markdown-it-hr-sandwiched-semantic-container": "^0.11.0",
41
42
  "@peaceroad/markdown-it-renderer-image": "^0.12.0",
42
- "@peaceroad/markdown-it-renderer-inline-text": "^0.7.0",
43
+ "@peaceroad/markdown-it-renderer-inline-text": "^0.8.0",
43
44
  "markdown-it-attrs": "^4.3.1",
44
45
  "markdown-it-sub": "^2.0.0",
45
46
  "markdown-it-sup": "^2.0.0",
46
47
  "p7d-markdown-it-p-captions": "^0.21.0"
47
48
  }
48
49
  }
49
-
@@ -2,18 +2,14 @@ import Token from 'markdown-it/lib/token.mjs'
2
2
  import {
3
3
  REG_ATTRS,
4
4
  isJapaneseChar,
5
+ isAsciiWordCode,
5
6
  hasCjkBreaksRule,
6
7
  isCjkBreaksRuleName,
7
8
  getRuntimeOpt,
9
+ hasRuntimeOverride,
8
10
  moveRuleAfter
9
11
  } from './token-utils.js'
10
12
 
11
- const isAsciiWordCode = (code) => {
12
- return (code >= 0x30 && code <= 0x39) ||
13
- (code >= 0x41 && code <= 0x5A) ||
14
- (code >= 0x61 && code <= 0x7A)
15
- }
16
-
17
13
  const trimTrailingSpaceTab = (text) => {
18
14
  if (!text) return text
19
15
  let end = text.length
@@ -25,10 +21,14 @@ const trimTrailingSpaceTab = (text) => {
25
21
  return end === text.length ? text : text.slice(0, end)
26
22
  }
27
23
 
24
+ const getStateSource = (state) => {
25
+ return state && typeof state.src === 'string' ? state.src : ''
26
+ }
27
+
28
28
  const registerTokenCompat = (md, baseOpt) => {
29
29
  const isCompatibleMode = (state) => {
30
30
  const override = state && state.env && state.env.__strongJaTokenOpt
31
- if (!override) return baseOpt.__strongJaIsCompatibleMode === true
31
+ if (!hasRuntimeOverride(override)) return baseOpt.__strongJaIsCompatibleMode === true
32
32
  const opt = getRuntimeOpt(state, baseOpt)
33
33
  return opt.__strongJaIsCompatibleMode === true
34
34
  }
@@ -82,6 +82,8 @@ const registerTokenCompat = (md, baseOpt) => {
82
82
  const normalizeSoftbreakSpacing = (state) => {
83
83
  if (isCompatibleMode(state)) return
84
84
  if (!state) return
85
+ const src = getStateSource(state)
86
+ if (!src || src.indexOf('\n') === -1) return
85
87
  if (baseOpt.hasCjkBreaks !== true && state.md) {
86
88
  baseOpt.hasCjkBreaks = hasCjkBreaksRule(state.md)
87
89
  }
@@ -164,11 +166,8 @@ const registerTokenCompat = (md, baseOpt) => {
164
166
  const restoreSoftbreaksAfterCjk = (state) => {
165
167
  if (isCompatibleMode(state)) return
166
168
  if (!state) return
167
- const overrideOpt = state.env && state.env.__strongJaTokenOpt
168
- if (overrideOpt) {
169
- const opt = getRuntimeOpt(state, baseOpt)
170
- if (opt.mditAttrs !== false) return
171
- }
169
+ const src = getStateSource(state)
170
+ if (!src || src.indexOf('\n') === -1 || src.indexOf('{') === -1) return
172
171
  if (!state.md || state.md.__strongJaRestoreSoftbreaksForAttrs !== true) return
173
172
  if (baseOpt.hasCjkBreaks !== true && state.md) {
174
173
  baseOpt.hasCjkBreaks = hasCjkBreaksRule(state.md)
@@ -239,11 +238,8 @@ const registerTokenCompat = (md, baseOpt) => {
239
238
  md.core.ruler.before('linkify', 'strong_ja_token_pre_attrs', (state) => {
240
239
  if (isCompatibleMode(state)) return
241
240
  if (!state || !state.tokens) return
242
- const overrideOpt = state.env && state.env.__strongJaTokenOpt
243
- if (overrideOpt) {
244
- const opt = getRuntimeOpt(state, baseOpt)
245
- if (opt.mditAttrs === false) return
246
- }
241
+ const src = getStateSource(state)
242
+ if (!src || src.indexOf('{') === -1 || src.indexOf('}') === -1) return
247
243
  for (let i = 0; i < state.tokens.length; i++) {
248
244
  const token = state.tokens[i]
249
245
  if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
package/src/token-core.js CHANGED
@@ -2,14 +2,14 @@ import { isWhiteSpace } from 'markdown-it/lib/common/utils.mjs'
2
2
  import Token from 'markdown-it/lib/token.mjs'
3
3
  import {
4
4
  CHAR_ASTERISK,
5
- CHAR_SPACE,
6
- CHAR_TAB,
7
5
  CHAR_NEWLINE,
8
- CHAR_IDEOGRAPHIC_SPACE,
9
6
  isJapaneseChar,
7
+ isAsciiWordCode,
8
+ isSoftSpaceCode,
10
9
  MODE_FLAG_COMPATIBLE,
11
10
  MODE_FLAG_AGGRESSIVE,
12
11
  MODE_FLAG_JAPANESE_PLUS,
12
+ hasRuntimeOverride,
13
13
  getRuntimeOpt
14
14
  } from './token-utils.js'
15
15
 
@@ -17,10 +17,7 @@ const SCAN_DELIMS_PATCHED = Symbol.for('strongJaTokenScanDelimsPatched')
17
17
  const SINGLE_STAR_LOOKAROUND_MAX = 16
18
18
  const PREV_STAR_HAS_OPENER = 1
19
19
  const PREV_STAR_HAS_JP_BETWEEN = 2
20
-
21
- const isSoftSpaceCode = (code) => {
22
- return code === CHAR_SPACE || code === CHAR_TAB || code === CHAR_IDEOGRAPHIC_SPACE
23
- }
20
+ const SCAN_DELIMS_LOOKUP_KEY = Symbol.for('strongJaTokenScanDelimsLookup')
24
21
 
25
22
  const isPlusQuoteWrapperOpen = (code) => {
26
23
  return code === 0x2018 || // ‘
@@ -254,12 +251,6 @@ const isSingleStarClosingBoundary = (code) => {
254
251
  isClosingBracketLike(code)
255
252
  }
256
253
 
257
- const isAsciiAlphaNum = (code) => {
258
- return (code >= 0x30 && code <= 0x39) ||
259
- (code >= 0x41 && code <= 0x5A) ||
260
- (code >= 0x61 && code <= 0x7A)
261
- }
262
-
263
254
  const isAsciiGuardOpenWrapper = (code) => {
264
255
  return code === 0x22 || // "
265
256
  code === 0x27 || // '
@@ -280,7 +271,58 @@ const isAsciiGuardCloseWrapper = (code) => {
280
271
  code === 0x60 // `
281
272
  }
282
273
 
283
- const findPrevNonSpaceIndex = (src, start) => {
274
+ const buildScanDelimsLookupCache = (src) => {
275
+ const len = typeof src === 'string' ? src.length : 0
276
+ const prevNonSpaceSameLine = new Int32Array(len)
277
+ const nextNonSpaceSameLine = new Int32Array(len)
278
+ prevNonSpaceSameLine.fill(-1)
279
+ nextNonSpaceSameLine.fill(-1)
280
+
281
+ let prev = -1
282
+ for (let i = 0; i < len; i++) {
283
+ const code = src.charCodeAt(i)
284
+ if (code === CHAR_NEWLINE) {
285
+ prev = -1
286
+ continue
287
+ }
288
+ if (!isSoftSpaceCode(code)) prev = i
289
+ prevNonSpaceSameLine[i] = prev
290
+ }
291
+
292
+ let next = -1
293
+ for (let i = len - 1; i >= 0; i--) {
294
+ const code = src.charCodeAt(i)
295
+ if (code === CHAR_NEWLINE) {
296
+ next = -1
297
+ continue
298
+ }
299
+ if (!isSoftSpaceCode(code)) next = i
300
+ nextNonSpaceSameLine[i] = next
301
+ }
302
+
303
+ return {
304
+ src,
305
+ prevNonSpaceSameLine,
306
+ nextNonSpaceSameLine
307
+ }
308
+ }
309
+
310
+ const getScanDelimsLookupCache = (state) => {
311
+ if (!state || typeof state.src !== 'string') return null
312
+ const cached = state[SCAN_DELIMS_LOOKUP_KEY]
313
+ if (cached && cached.src === state.src) return cached
314
+ const next = buildScanDelimsLookupCache(state.src)
315
+ state[SCAN_DELIMS_LOOKUP_KEY] = next
316
+ return next
317
+ }
318
+
319
+ const findPrevNonSpaceIndex = (src, start, lookupCache = null) => {
320
+ if (start < 0) return -1
321
+ if (lookupCache &&
322
+ lookupCache.src === src &&
323
+ start < lookupCache.prevNonSpaceSameLine.length) {
324
+ return lookupCache.prevNonSpaceSameLine[start]
325
+ }
284
326
  for (let i = start; i >= 0; i--) {
285
327
  const code = src.charCodeAt(i)
286
328
  if (code === CHAR_NEWLINE) return -1
@@ -290,7 +332,14 @@ const findPrevNonSpaceIndex = (src, start) => {
290
332
  return -1
291
333
  }
292
334
 
293
- const findNextNonSpaceIndex = (src, start, max) => {
335
+ const findNextNonSpaceIndex = (src, start, max, lookupCache = null) => {
336
+ if (lookupCache &&
337
+ lookupCache.src === src &&
338
+ start >= 0 &&
339
+ start < lookupCache.nextNonSpaceSameLine.length) {
340
+ const next = lookupCache.nextNonSpaceSameLine[start]
341
+ return next !== -1 && next < max ? next : -1
342
+ }
294
343
  for (let i = start; i < max; i++) {
295
344
  const code = src.charCodeAt(i)
296
345
  if (code === CHAR_NEWLINE) return -1
@@ -300,30 +349,30 @@ const findNextNonSpaceIndex = (src, start, max) => {
300
349
  return -1
301
350
  }
302
351
 
303
- const hasAsciiStartAfterOptionalOpenWrappers = (src, index, max) => {
352
+ const hasAsciiStartAfterOptionalOpenWrappers = (src, index, max, lookupCache = null) => {
304
353
  let i = index
305
354
  // Two wrappers are enough for common shapes: * [ "word" ]*
306
355
  for (let wrappers = 0; wrappers < 2 && i >= 0 && i < max; wrappers++) {
307
356
  const code = src.charCodeAt(i)
308
357
  if (!isAsciiGuardOpenWrapper(code)) break
309
- i = findNextNonSpaceIndex(src, i + 1, max)
358
+ i = findNextNonSpaceIndex(src, i + 1, max, lookupCache)
310
359
  if (i === -1) return false
311
360
  }
312
361
  if (i < 0 || i >= max) return false
313
- return isAsciiAlphaNum(src.charCodeAt(i))
362
+ return isAsciiWordCode(src.charCodeAt(i))
314
363
  }
315
364
 
316
- const hasAsciiEndBeforeOptionalCloseWrappers = (src, index) => {
365
+ const hasAsciiEndBeforeOptionalCloseWrappers = (src, index, lookupCache = null) => {
317
366
  let i = index
318
367
  // Two wrappers are enough for common shapes: *["word"] *
319
368
  for (let wrappers = 0; wrappers < 2 && i >= 0; wrappers++) {
320
369
  const code = src.charCodeAt(i)
321
370
  if (!isAsciiGuardCloseWrapper(code)) break
322
- i = findPrevNonSpaceIndex(src, i - 1)
371
+ i = findPrevNonSpaceIndex(src, i - 1, lookupCache)
323
372
  if (i === -1) return false
324
373
  }
325
374
  if (i < 0) return false
326
- return isAsciiAlphaNum(src.charCodeAt(i))
375
+ return isAsciiWordCode(src.charCodeAt(i))
327
376
  }
328
377
 
329
378
  const isMarkdownStructuralOpenWrapper = (code) => {
@@ -353,7 +402,17 @@ const isSentenceBoundaryStop = (code) => {
353
402
  code === 0x2049 // ⁉
354
403
  }
355
404
 
356
- const findPrevNonSpaceLimited = (src, start, maxLook) => {
405
+ const findPrevNonSpaceLimited = (src, start, maxLook, lookupCache = null) => {
406
+ if (lookupCache &&
407
+ lookupCache.src === src &&
408
+ start >= 0 &&
409
+ start < lookupCache.prevNonSpaceSameLine.length) {
410
+ const prev = lookupCache.prevNonSpaceSameLine[start]
411
+ if (prev !== -1 && (start - prev) < maxLook) {
412
+ return src.charCodeAt(prev)
413
+ }
414
+ return 0
415
+ }
357
416
  let looked = 0
358
417
  for (let i = start; i >= 0; i--) {
359
418
  if (looked >= maxLook) break
@@ -366,7 +425,17 @@ const findPrevNonSpaceLimited = (src, start, maxLook) => {
366
425
  return 0
367
426
  }
368
427
 
369
- const findNextNonSpaceLimited = (src, start, max, maxLook) => {
428
+ const findNextNonSpaceLimited = (src, start, max, maxLook, lookupCache = null) => {
429
+ if (lookupCache &&
430
+ lookupCache.src === src &&
431
+ start >= 0 &&
432
+ start < lookupCache.nextNonSpaceSameLine.length) {
433
+ const next = lookupCache.nextNonSpaceSameLine[start]
434
+ if (next !== -1 && next < max && (next - start) < maxLook) {
435
+ return src.charCodeAt(next)
436
+ }
437
+ return 0
438
+ }
370
439
  let looked = 0
371
440
  for (let i = start; i < max; i++) {
372
441
  if (looked >= maxLook) break
@@ -379,13 +448,13 @@ const findNextNonSpaceLimited = (src, start, max, maxLook) => {
379
448
  return 0
380
449
  }
381
450
 
382
- const hasJapaneseContextForBracketWrapper = (src, start, pos, max, lastChar, nextChar) => {
451
+ const hasJapaneseContextForBracketWrapper = (src, start, pos, max, lastChar, nextChar, lookupCache = null) => {
383
452
  if (isWrapperOpenLike(nextChar)) {
384
- const right = findNextNonSpaceLimited(src, pos, max, SINGLE_STAR_LOOKAROUND_MAX)
453
+ const right = findNextNonSpaceLimited(src, pos, max, SINGLE_STAR_LOOKAROUND_MAX, lookupCache)
385
454
  if (isJapaneseChar(right)) return true
386
455
  }
387
456
  if (isWrapperCloseLike(lastChar)) {
388
- const left = findPrevNonSpaceLimited(src, start - 2, SINGLE_STAR_LOOKAROUND_MAX)
457
+ const left = findPrevNonSpaceLimited(src, start - 2, SINGLE_STAR_LOOKAROUND_MAX, lookupCache)
389
458
  if (isJapaneseChar(left)) return true
390
459
  }
391
460
  return false
@@ -415,7 +484,8 @@ const scanPrevSingleStarContextFlags = (src, start) => {
415
484
  }
416
485
 
417
486
  const ensurePrevStarFlags = (src, start, prevStarFlags) => {
418
- return prevStarFlags >= 0 ? prevStarFlags : scanPrevSingleStarContextFlags(src, start)
487
+ if (prevStarFlags >= 0) return prevStarFlags
488
+ return scanPrevSingleStarContextFlags(src, start)
419
489
  }
420
490
 
421
491
  const fixTrailingStrong = (tokens, onChangeStart = null) => {
@@ -696,7 +766,7 @@ const patchScanDelims = (md) => {
696
766
 
697
767
  const baseOpt = this.md ? this.md.__strongJaTokenOpt : null
698
768
  const overrideOpt = this.env && this.env.__strongJaTokenOpt
699
- const opt = overrideOpt ? getRuntimeOpt(this, baseOpt) : baseOpt
769
+ const opt = hasRuntimeOverride(overrideOpt) ? getRuntimeOpt(this, baseOpt) : baseOpt
700
770
  if (!opt) {
701
771
  return base
702
772
  }
@@ -707,6 +777,7 @@ const patchScanDelims = (md) => {
707
777
  const plusMode = (modeFlags & MODE_FLAG_JAPANESE_PLUS) !== 0
708
778
  const aggressiveMode = (modeFlags & MODE_FLAG_AGGRESSIVE) !== 0
709
779
  const max = this.posMax
780
+ let lookupCache = null
710
781
  const lastChar = start > 0 ? src.charCodeAt(start - 1) : 0x20
711
782
 
712
783
  const count = base && base.length ? base.length : 1
@@ -719,7 +790,15 @@ const patchScanDelims = (md) => {
719
790
  const rightJapanese = isJapaneseChar(nextChar)
720
791
  let hasJapaneseContext = leftJapanese || rightJapanese
721
792
  if (!hasJapaneseContext && count === 1) {
722
- hasJapaneseContext = hasJapaneseContextForBracketWrapper(src, start, pos, max, lastChar, nextChar)
793
+ hasJapaneseContext = hasJapaneseContextForBracketWrapper(
794
+ src,
795
+ start,
796
+ pos,
797
+ max,
798
+ lastChar,
799
+ nextChar,
800
+ lookupCache || (lookupCache = getScanDelimsLookupCache(this))
801
+ )
723
802
  }
724
803
  if (!hasJapaneseContext && count === 1 && isExtraSingleStarClosePunct(lastChar)) {
725
804
  prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
@@ -734,22 +813,31 @@ const patchScanDelims = (md) => {
734
813
  let isLastWhiteSpace = isWhiteSpace(lastChar) || isSoftSpaceCode(lastChar)
735
814
  let isNextWhiteSpace = isWhiteSpace(nextChar) || isSoftSpaceCode(nextChar)
736
815
  if (isLastWhiteSpace && isSoftSpaceCode(lastChar)) {
737
- const prevNonSpaceIdx = findPrevNonSpaceIndex(src, start - 2)
816
+ const prevNonSpaceIdx = findPrevNonSpaceIndex(
817
+ src,
818
+ start - 2,
819
+ lookupCache || (lookupCache = getScanDelimsLookupCache(this))
820
+ )
738
821
  if (prevNonSpaceIdx !== -1) {
739
822
  const prevNonSpaceLocal = src.charCodeAt(prevNonSpaceIdx)
740
823
  const plusStrictAsciiBoundary = plusMode &&
741
- hasAsciiEndBeforeOptionalCloseWrappers(src, prevNonSpaceIdx)
824
+ hasAsciiEndBeforeOptionalCloseWrappers(src, prevNonSpaceIdx, lookupCache)
742
825
  if (prevNonSpaceLocal !== CHAR_ASTERISK && !plusStrictAsciiBoundary) {
743
826
  isLastWhiteSpace = false
744
827
  }
745
828
  }
746
829
  }
747
830
  if (isNextWhiteSpace && isSoftSpaceCode(nextChar)) {
748
- const nextNonSpaceIdx = findNextNonSpaceIndex(src, pos, max)
831
+ const nextNonSpaceIdx = findNextNonSpaceIndex(
832
+ src,
833
+ pos,
834
+ max,
835
+ lookupCache || (lookupCache = getScanDelimsLookupCache(this))
836
+ )
749
837
  if (nextNonSpaceIdx !== -1) {
750
838
  const nextNonSpace = src.charCodeAt(nextNonSpaceIdx)
751
839
  const plusStrictAsciiBoundary = plusMode &&
752
- hasAsciiStartAfterOptionalOpenWrappers(src, nextNonSpaceIdx, max)
840
+ hasAsciiStartAfterOptionalOpenWrappers(src, nextNonSpaceIdx, max, lookupCache)
753
841
  if (nextNonSpace !== CHAR_ASTERISK && !plusStrictAsciiBoundary) {
754
842
  isNextWhiteSpace = false
755
843
  }
@@ -771,8 +859,10 @@ const patchScanDelims = (md) => {
771
859
  if (!aggressiveMode && count === 1) {
772
860
  // Keep local directionality to avoid degrading markdown-it-valid runs,
773
861
  // e.g. `[。*a**](u)` where the first `*` should remain opener-only.
774
- const rightIsBoundary = isSingleStarClosingBoundary(nextChar) || isWrapperOpenLike(nextChar)
775
- const leftIsBoundary = isSingleStarBoundary(lastChar) || isWrapperCloseLike(lastChar)
862
+ const rightIsOpenWrapper = isWrapperOpenLike(nextChar)
863
+ const leftIsCloseWrapper = isWrapperCloseLike(lastChar)
864
+ const rightIsBoundary = isSingleStarClosingBoundary(nextChar) || rightIsOpenWrapper
865
+ const leftIsBoundary = isSingleStarBoundary(lastChar) || leftIsCloseWrapper
776
866
  if (leftJapanese && !rightJapanese && !rightIsBoundary) {
777
867
  prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
778
868
  if ((prevStarFlags & PREV_STAR_HAS_OPENER) === 0) {
@@ -781,28 +871,30 @@ const patchScanDelims = (md) => {
781
871
  } else if (!leftJapanese && rightJapanese && !leftIsBoundary) {
782
872
  relaxedOpen = false
783
873
  }
784
- const rightIsOpenWrapper = isWrapperOpenLike(nextChar)
785
- const leftIsCloseWrapper = isWrapperCloseLike(lastChar)
786
- prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
787
- const hasPrevJapaneseOpener = (prevStarFlags & PREV_STAR_HAS_OPENER) !== 0
788
- const hasJapaneseSincePrevStar = (prevStarFlags & PREV_STAR_HAS_JP_BETWEEN) !== 0
789
874
  const leftIsExtraClosePunct = isExtraSingleStarClosePunct(lastChar)
790
- const canForceCloseByPunct = leftIsExtraClosePunct && hasJapaneseSincePrevStar
791
- if (leftJapanese &&
792
- rightIsOpenWrapper &&
793
- !hasPrevJapaneseOpener &&
794
- !isMarkdownStructuralOpenWrapper(nextChar)) {
795
- forceOpen = true
796
- forceClose = false
797
- } else if (leftIsCloseWrapper && rightJapanese && hasPrevJapaneseOpener) {
798
- forceOpen = false
799
- forceClose = true
800
- } else if ((leftIsCloseWrapper || canForceCloseByPunct) &&
801
- !rightJapanese &&
802
- !rightIsBoundary &&
803
- hasPrevJapaneseOpener) {
804
- forceOpen = false
805
- forceClose = true
875
+ const canCheckForceOpen =
876
+ leftJapanese && rightIsOpenWrapper && !isMarkdownStructuralOpenWrapper(nextChar)
877
+ const canCheckForceClose =
878
+ (leftIsCloseWrapper && rightJapanese) ||
879
+ ((leftIsCloseWrapper || leftIsExtraClosePunct) && !rightJapanese && !rightIsBoundary)
880
+ if (canCheckForceOpen || canCheckForceClose) {
881
+ prevStarFlags = ensurePrevStarFlags(src, start, prevStarFlags)
882
+ const hasPrevJapaneseOpener = (prevStarFlags & PREV_STAR_HAS_OPENER) !== 0
883
+ const hasJapaneseSincePrevStar = (prevStarFlags & PREV_STAR_HAS_JP_BETWEEN) !== 0
884
+ const canForceCloseByPunct = leftIsExtraClosePunct && hasJapaneseSincePrevStar
885
+ if (canCheckForceOpen && !hasPrevJapaneseOpener) {
886
+ forceOpen = true
887
+ forceClose = false
888
+ } else if (leftIsCloseWrapper && rightJapanese && hasPrevJapaneseOpener) {
889
+ forceOpen = false
890
+ forceClose = true
891
+ } else if ((leftIsCloseWrapper || canForceCloseByPunct) &&
892
+ !rightJapanese &&
893
+ !rightIsBoundary &&
894
+ hasPrevJapaneseOpener) {
895
+ forceOpen = false
896
+ forceClose = true
897
+ }
806
898
  }
807
899
  }
808
900
  const finalOpen = forceOpen === null ? ((base && base.can_open) || relaxedOpen) : forceOpen
@@ -1,6 +1,6 @@
1
1
  import Token from 'markdown-it/lib/token.mjs'
2
2
  import { isWhiteSpace } from 'markdown-it/lib/common/utils.mjs'
3
- import { getReferenceCount } from './token-utils.js'
3
+ import { cloneMap, getReferenceCount } from './token-utils.js'
4
4
 
5
5
  const CHAR_OPEN_BRACKET = 0x5B // [
6
6
  const CHAR_CLOSE_BRACKET = 0x5D // ]
@@ -80,11 +80,6 @@ const getNormalizeRef = (state) => {
80
80
  }
81
81
 
82
82
 
83
- const cloneMap = (map) => {
84
- if (!map || !Array.isArray(map)) return null
85
- return [map[0], map[1]]
86
- }
87
-
88
83
  const getMapFromTokenRange = (tokens, startIdx, endIdx) => {
89
84
  if (!tokens || startIdx > endIdx) return null
90
85
  let startLine = null
@@ -120,10 +115,7 @@ const cloneTextToken = (source, content) => {
120
115
  }
121
116
 
122
117
  const applyBracketSegmentFlags = (token, seg) => {
123
- if (seg === '[' || seg === ']') {
124
- token.__strongJaHasBracket = true
125
- token.__strongJaBracketAtomic = true
126
- } else if (seg === '[]') {
118
+ if (seg === '[' || seg === ']' || seg === '[]') {
127
119
  token.__strongJaHasBracket = true
128
120
  token.__strongJaBracketAtomic = true
129
121
  } else {
@@ -641,16 +633,21 @@ const collectBrokenMarkLinkMergeRemovals = (tokens) => {
641
633
 
642
634
  const applyBrokenMarkLinkMergeRemovals = (tokens, removals, onChangeStart = null) => {
643
635
  if (!removals || removals.length === 0) return false
644
- const removeFlags = new Array(tokens.length).fill(false)
645
636
  for (let idx = removals.length - 1; idx >= 0; idx--) {
646
- const removal = removals[idx]
647
- if (onChangeStart) onChangeStart(removal.closeIdx)
648
- removeFlags[removal.closeIdx] = true
649
- removeFlags[removal.reopenIdx] = true
637
+ if (onChangeStart) onChangeStart(removals[idx].closeIdx)
650
638
  }
651
639
  const kept = []
640
+ let removalIdx = 0
641
+ let nextRemoval = removals[removalIdx]
652
642
  for (let idx = 0; idx < tokens.length; idx++) {
653
- if (!removeFlags[idx]) kept.push(tokens[idx])
643
+ if (nextRemoval && (idx === nextRemoval.closeIdx || idx === nextRemoval.reopenIdx)) {
644
+ if (idx === nextRemoval.reopenIdx) {
645
+ removalIdx++
646
+ nextRemoval = removals[removalIdx]
647
+ }
648
+ continue
649
+ }
650
+ kept.push(tokens[idx])
654
651
  }
655
652
  tokens.splice(0, tokens.length, ...kept)
656
653
  return true