@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/README.md +15 -3
- package/index.js +31 -22
- package/package.json +8 -6
- package/src/token-compat.js +13 -18
- package/src/token-core.js +189 -76
- package/src/token-link-utils.js +386 -193
- package/src/token-postprocess/broken-ref.js +482 -0
- package/src/token-postprocess/guards.js +217 -198
- package/src/token-postprocess/orchestrator.js +295 -385
- package/src/token-utils.js +73 -27
|
@@ -1,139 +1,30 @@
|
|
|
1
1
|
import Token from 'markdown-it/lib/token.mjs'
|
|
2
2
|
import { buildLinkCloseMap, convertCollapsedReferenceLinks, mergeBrokenMarksAroundLinks } from '../token-link-utils.js'
|
|
3
|
+
import { computeMaxBrokenRefRepairPass, runBrokenRefRepairs } from './broken-ref.js'
|
|
3
4
|
import {
|
|
4
5
|
rebuildInlineLevels,
|
|
6
|
+
rebuildInlineLevelsFrom,
|
|
5
7
|
fixEmOuterStrongSequence,
|
|
6
8
|
fixLeadingAsteriskEm,
|
|
7
9
|
fixTrailingStrong
|
|
8
10
|
} from '../token-core.js'
|
|
9
11
|
import {
|
|
10
|
-
getInlineWrapperBase,
|
|
11
12
|
getRuntimeOpt,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
MODE_FLAG_JAPANESE_PLUS,
|
|
15
|
-
MODE_FLAG_JAPANESE_ANY
|
|
13
|
+
hasRuntimeOverride,
|
|
14
|
+
getReferenceCount
|
|
16
15
|
} from '../token-utils.js'
|
|
17
16
|
import {
|
|
18
17
|
hasMarkerChars,
|
|
19
|
-
isAsteriskEmphasisToken,
|
|
20
18
|
hasJapaneseContextInRange,
|
|
21
19
|
hasEmphasisSignalInRange,
|
|
22
|
-
hasTextMarkerCharsInRange,
|
|
23
20
|
buildAsteriskWrapperPrefixStats,
|
|
24
|
-
shouldAttemptBrokenRefRewrite,
|
|
25
21
|
scanInlinePostprocessSignals
|
|
26
22
|
} from './guards.js'
|
|
27
23
|
import {
|
|
28
|
-
BROKEN_REF_FAST_PATH_RESULT_NO_ACTIVE_SIGNATURE,
|
|
29
|
-
BROKEN_REF_FAST_PATH_RESULT_NO_MATCH,
|
|
30
|
-
applyBrokenRefTokenOnlyFastPath,
|
|
31
24
|
tryFixTailPatternTokenOnly,
|
|
32
25
|
tryFixTailDanglingStrongCloseTokenOnly
|
|
33
26
|
} from './fastpaths.js'
|
|
34
27
|
|
|
35
|
-
const scanBrokenRefState = (text, out) => {
|
|
36
|
-
if (!text || text.indexOf('[') === -1) {
|
|
37
|
-
out.depth = 0
|
|
38
|
-
out.brokenEnd = false
|
|
39
|
-
out.tailOpen = -1
|
|
40
|
-
return out
|
|
41
|
-
}
|
|
42
|
-
let depth = 0
|
|
43
|
-
let lastOpen = -1
|
|
44
|
-
let lastClose = -1
|
|
45
|
-
for (let i = 0; i < text.length; i++) {
|
|
46
|
-
const ch = text.charCodeAt(i)
|
|
47
|
-
if (ch === 0x5B) {
|
|
48
|
-
depth++
|
|
49
|
-
lastOpen = i
|
|
50
|
-
} else if (ch === 0x5D) {
|
|
51
|
-
if (depth > 0) depth--
|
|
52
|
-
lastClose = i
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
out.depth = depth
|
|
56
|
-
out.brokenEnd = depth > 0 && lastOpen > lastClose
|
|
57
|
-
out.tailOpen = out.brokenEnd ? lastOpen : -1
|
|
58
|
-
return out
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
const resetBrokenRefScanState = (scanState) => {
|
|
62
|
-
if (!scanState) return scanState
|
|
63
|
-
scanState.depth = 0
|
|
64
|
-
scanState.brokenEnd = false
|
|
65
|
-
scanState.tailOpen = -1
|
|
66
|
-
return scanState
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const updateBracketDepth = (text, depth) => {
|
|
70
|
-
if (!text || depth <= 0) return depth
|
|
71
|
-
for (let i = 0; i < text.length; i++) {
|
|
72
|
-
const ch = text.charCodeAt(i)
|
|
73
|
-
if (ch === 0x5B) {
|
|
74
|
-
depth++
|
|
75
|
-
} else if (ch === 0x5D) {
|
|
76
|
-
if (depth > 0) {
|
|
77
|
-
depth--
|
|
78
|
-
if (depth === 0) return 0
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
return depth
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
const expandSegmentEndForWrapperBalance = (tokens, startIdx, endIdx) => {
|
|
86
|
-
if (!tokens || startIdx < 0 || endIdx < startIdx) return endIdx
|
|
87
|
-
const depthMap = new Map()
|
|
88
|
-
let openDepthTotal = 0
|
|
89
|
-
let expandedEnd = endIdx
|
|
90
|
-
|
|
91
|
-
for (let i = startIdx; i <= expandedEnd; i++) {
|
|
92
|
-
const token = tokens[i]
|
|
93
|
-
if (!token || !token.type) continue
|
|
94
|
-
if ((token.type === 'strong_open' || token.type === 'strong_close' || token.type === 'em_open' || token.type === 'em_close') &&
|
|
95
|
-
!isAsteriskEmphasisToken(token)) {
|
|
96
|
-
continue
|
|
97
|
-
}
|
|
98
|
-
const base = getInlineWrapperBase(token.type)
|
|
99
|
-
if (!base) continue
|
|
100
|
-
if (token.type.endsWith('_open')) {
|
|
101
|
-
depthMap.set(base, (depthMap.get(base) || 0) + 1)
|
|
102
|
-
openDepthTotal++
|
|
103
|
-
continue
|
|
104
|
-
}
|
|
105
|
-
const prev = depthMap.get(base) || 0
|
|
106
|
-
if (prev > 0) {
|
|
107
|
-
depthMap.set(base, prev - 1)
|
|
108
|
-
openDepthTotal--
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
while (openDepthTotal > 0 && expandedEnd + 1 < tokens.length) {
|
|
113
|
-
expandedEnd++
|
|
114
|
-
const token = tokens[expandedEnd]
|
|
115
|
-
if (!token || !token.type) continue
|
|
116
|
-
if ((token.type === 'strong_open' || token.type === 'strong_close' || token.type === 'em_open' || token.type === 'em_close') &&
|
|
117
|
-
!isAsteriskEmphasisToken(token)) {
|
|
118
|
-
continue
|
|
119
|
-
}
|
|
120
|
-
const base = getInlineWrapperBase(token.type)
|
|
121
|
-
if (!base) continue
|
|
122
|
-
if (token.type.endsWith('_open')) {
|
|
123
|
-
depthMap.set(base, (depthMap.get(base) || 0) + 1)
|
|
124
|
-
openDepthTotal++
|
|
125
|
-
continue
|
|
126
|
-
}
|
|
127
|
-
const prev = depthMap.get(base) || 0
|
|
128
|
-
if (prev > 0) {
|
|
129
|
-
depthMap.set(base, prev - 1)
|
|
130
|
-
openDepthTotal--
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
return openDepthTotal > 0 ? -1 : expandedEnd
|
|
135
|
-
}
|
|
136
|
-
|
|
137
28
|
const fallbackMarkupByType = (type) => {
|
|
138
29
|
if (type === 'strong_open' || type === 'strong_close') return '**'
|
|
139
30
|
if (type === 'em_open' || type === 'em_close') return '*'
|
|
@@ -151,7 +42,7 @@ const makeTokenLiteralText = (token) => {
|
|
|
151
42
|
token.info = ''
|
|
152
43
|
}
|
|
153
44
|
|
|
154
|
-
const sanitizeEmStrongBalance = (tokens) => {
|
|
45
|
+
const sanitizeEmStrongBalance = (tokens, onChangeStart = null) => {
|
|
155
46
|
if (!tokens || tokens.length === 0) return false
|
|
156
47
|
const stack = []
|
|
157
48
|
let changed = false
|
|
@@ -168,6 +59,7 @@ const sanitizeEmStrongBalance = (tokens) => {
|
|
|
168
59
|
stack.pop()
|
|
169
60
|
continue
|
|
170
61
|
}
|
|
62
|
+
if (onChangeStart) onChangeStart(i)
|
|
171
63
|
makeTokenLiteralText(token)
|
|
172
64
|
changed = true
|
|
173
65
|
}
|
|
@@ -175,6 +67,7 @@ const sanitizeEmStrongBalance = (tokens) => {
|
|
|
175
67
|
const entry = stack[i]
|
|
176
68
|
const token = tokens[entry.idx]
|
|
177
69
|
if (!token) continue
|
|
70
|
+
if (onChangeStart) onChangeStart(entry.idx)
|
|
178
71
|
makeTokenLiteralText(token)
|
|
179
72
|
changed = true
|
|
180
73
|
}
|
|
@@ -188,179 +81,92 @@ const getPostprocessMetrics = (state) => {
|
|
|
188
81
|
return metrics
|
|
189
82
|
}
|
|
190
83
|
|
|
191
|
-
const
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
84
|
+
const buildInlinePostprocessFacts = (children, inlineContent) => {
|
|
85
|
+
const preScan = scanInlinePostprocessSignals(children)
|
|
86
|
+
return {
|
|
87
|
+
hasBracketText: inlineContent.indexOf('[') !== -1 || inlineContent.indexOf(']') !== -1,
|
|
88
|
+
hasEmphasis: preScan.hasEmphasis,
|
|
89
|
+
hasLinkOpen: preScan.hasLinkOpen,
|
|
90
|
+
hasLinkClose: preScan.hasLinkClose,
|
|
91
|
+
hasCodeInline: preScan.hasCodeInline,
|
|
92
|
+
linkCloseMap: undefined,
|
|
93
|
+
wrapperPrefixStats: undefined,
|
|
94
|
+
rebuildLevelStart: undefined
|
|
197
95
|
}
|
|
198
|
-
table[key] = (table[key] || 0) + 1
|
|
199
96
|
}
|
|
200
97
|
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
let hasEmphasis = false
|
|
210
|
-
let hasLinkClose = false
|
|
211
|
-
|
|
212
|
-
for (let j = 0; j < children.length; j++) {
|
|
213
|
-
const child = children[j]
|
|
214
|
-
if (!child) continue
|
|
215
|
-
|
|
216
|
-
if (child.type === 'text' && child.content) {
|
|
217
|
-
const text = child.content
|
|
218
|
-
const hasOpenBracket = text.indexOf('[') !== -1
|
|
219
|
-
const hasCloseBracket = text.indexOf(']') !== -1
|
|
220
|
-
if (!hasBracketText && (hasOpenBracket || hasCloseBracket)) {
|
|
221
|
-
hasBracketText = true
|
|
222
|
-
}
|
|
223
|
-
if (brokenRefStart === -1) {
|
|
224
|
-
if (hasOpenBracket) {
|
|
225
|
-
const scan = scanBrokenRefState(text, scanState)
|
|
226
|
-
if (scan.brokenEnd) {
|
|
227
|
-
brokenRefStart = j
|
|
228
|
-
brokenRefDepth = scan.depth
|
|
229
|
-
brokenRefStartTextOffset = scan.tailOpen > 0 ? scan.tailOpen : 0
|
|
230
|
-
continue
|
|
231
|
-
}
|
|
232
|
-
}
|
|
233
|
-
} else if (hasOpenBracket || hasCloseBracket) {
|
|
234
|
-
brokenRefDepth = updateBracketDepth(text, brokenRefDepth)
|
|
235
|
-
if (brokenRefDepth <= 0) {
|
|
236
|
-
brokenRefStart = -1
|
|
237
|
-
brokenRefDepth = 0
|
|
238
|
-
brokenRefStartTextOffset = 0
|
|
239
|
-
}
|
|
240
|
-
}
|
|
241
|
-
}
|
|
98
|
+
const ensureInlineLinkCloseMap = (facts, tokens) => {
|
|
99
|
+
if (!tokens || tokens.length === 0) return new Map()
|
|
100
|
+
if (!facts) return buildLinkCloseMap(tokens, 0, tokens.length - 1)
|
|
101
|
+
if (facts.linkCloseMap === undefined) {
|
|
102
|
+
facts.linkCloseMap = buildLinkCloseMap(tokens, 0, tokens.length - 1)
|
|
103
|
+
}
|
|
104
|
+
return facts.linkCloseMap
|
|
105
|
+
}
|
|
242
106
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
}
|
|
249
|
-
if (brokenRefStart === -1 || child.type !== 'link_open') continue
|
|
250
|
-
if (brokenRefDepth <= 0) {
|
|
251
|
-
brokenRefStart = -1
|
|
252
|
-
brokenRefDepth = 0
|
|
253
|
-
brokenRefStartTextOffset = 0
|
|
254
|
-
continue
|
|
255
|
-
}
|
|
256
|
-
if (linkCloseMap === null) {
|
|
257
|
-
linkCloseMap = buildLinkCloseMap(children, 0, children.length - 1)
|
|
258
|
-
}
|
|
259
|
-
const closeIdx = linkCloseMap.get(j) ?? -1
|
|
260
|
-
if (closeIdx === -1) continue
|
|
261
|
-
bumpPostprocessMetric(metrics, 'brokenRefFlow', 'candidate')
|
|
262
|
-
let segmentEnd = expandSegmentEndForWrapperBalance(children, brokenRefStart, closeIdx)
|
|
263
|
-
if (segmentEnd === -1) {
|
|
264
|
-
bumpPostprocessMetric(metrics, 'brokenRefFlow', 'wrapper-expand-fallback')
|
|
265
|
-
segmentEnd = closeIdx
|
|
266
|
-
}
|
|
267
|
-
if (!hasTextMarkerCharsInRange(children, brokenRefStart, segmentEnd, brokenRefStartTextOffset)) {
|
|
268
|
-
bumpPostprocessMetric(metrics, 'brokenRefFlow', 'skip-no-text-marker')
|
|
269
|
-
brokenRefStart = -1
|
|
270
|
-
brokenRefDepth = 0
|
|
271
|
-
brokenRefStartTextOffset = 0
|
|
272
|
-
continue
|
|
273
|
-
}
|
|
274
|
-
if (wrapperPrefixStats === null) {
|
|
275
|
-
wrapperPrefixStats = buildAsteriskWrapperPrefixStats(children)
|
|
276
|
-
}
|
|
277
|
-
if (!shouldAttemptBrokenRefRewrite(
|
|
278
|
-
children,
|
|
279
|
-
brokenRefStart,
|
|
280
|
-
segmentEnd,
|
|
281
|
-
brokenRefStartTextOffset,
|
|
282
|
-
wrapperPrefixStats
|
|
283
|
-
)) {
|
|
284
|
-
bumpPostprocessMetric(metrics, 'brokenRefFlow', 'skip-guard')
|
|
285
|
-
brokenRefStart = -1
|
|
286
|
-
brokenRefDepth = 0
|
|
287
|
-
brokenRefStartTextOffset = 0
|
|
288
|
-
continue
|
|
289
|
-
}
|
|
290
|
-
const fastPathResult = applyBrokenRefTokenOnlyFastPath(
|
|
291
|
-
children,
|
|
292
|
-
brokenRefStart,
|
|
293
|
-
segmentEnd,
|
|
294
|
-
linkCloseMap,
|
|
295
|
-
metrics,
|
|
296
|
-
bumpPostprocessMetric
|
|
297
|
-
)
|
|
298
|
-
if (fastPathResult === BROKEN_REF_FAST_PATH_RESULT_NO_ACTIVE_SIGNATURE) {
|
|
299
|
-
bumpPostprocessMetric(metrics, 'brokenRefFlow', 'skip-no-active-signature')
|
|
300
|
-
brokenRefStart = -1
|
|
301
|
-
brokenRefDepth = 0
|
|
302
|
-
brokenRefStartTextOffset = 0
|
|
303
|
-
continue
|
|
304
|
-
}
|
|
305
|
-
if (fastPathResult === BROKEN_REF_FAST_PATH_RESULT_NO_MATCH) {
|
|
306
|
-
bumpPostprocessMetric(metrics, 'brokenRefFlow', 'skip-no-fastpath-match')
|
|
307
|
-
brokenRefStart = -1
|
|
308
|
-
brokenRefDepth = 0
|
|
309
|
-
brokenRefStartTextOffset = 0
|
|
310
|
-
continue
|
|
311
|
-
}
|
|
312
|
-
bumpPostprocessMetric(metrics, 'brokenRefFlow', 'repaired')
|
|
313
|
-
return {
|
|
314
|
-
didRepair: true,
|
|
315
|
-
hasBracketText,
|
|
316
|
-
hasEmphasis,
|
|
317
|
-
hasLinkClose
|
|
318
|
-
}
|
|
107
|
+
const ensureInlineWrapperPrefixStats = (facts, tokens) => {
|
|
108
|
+
if (!tokens || tokens.length === 0) return null
|
|
109
|
+
if (!facts) return buildAsteriskWrapperPrefixStats(tokens)
|
|
110
|
+
if (facts.wrapperPrefixStats === undefined) {
|
|
111
|
+
facts.wrapperPrefixStats = buildAsteriskWrapperPrefixStats(tokens)
|
|
319
112
|
}
|
|
113
|
+
return facts.wrapperPrefixStats
|
|
114
|
+
}
|
|
320
115
|
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
116
|
+
const invalidateInlineDerivedCaches = (facts) => {
|
|
117
|
+
if (!facts) return
|
|
118
|
+
facts.linkCloseMap = undefined
|
|
119
|
+
facts.wrapperPrefixStats = undefined
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const markInlineLevelRebuildFrom = (facts, startIdx) => {
|
|
123
|
+
if (!facts) return
|
|
124
|
+
const from = startIdx > 0 ? startIdx : 0
|
|
125
|
+
if (facts.rebuildLevelStart === undefined || from < facts.rebuildLevelStart) {
|
|
126
|
+
facts.rebuildLevelStart = from
|
|
326
127
|
}
|
|
327
128
|
}
|
|
328
129
|
|
|
329
|
-
const
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
maxRepairPass++
|
|
338
|
-
}
|
|
130
|
+
const rebuildInlineLevelsForFacts = (tokens, facts) => {
|
|
131
|
+
if (!facts || facts.rebuildLevelStart === undefined) {
|
|
132
|
+
rebuildInlineLevels(tokens)
|
|
133
|
+
} else {
|
|
134
|
+
rebuildInlineLevelsFrom(tokens, facts.rebuildLevelStart)
|
|
135
|
+
}
|
|
136
|
+
if (facts) {
|
|
137
|
+
facts.rebuildLevelStart = undefined
|
|
339
138
|
}
|
|
340
|
-
return maxRepairPass
|
|
341
139
|
}
|
|
342
140
|
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
let hasBracketText = false
|
|
347
|
-
let hasEmphasis = false
|
|
348
|
-
let hasLinkClose = false
|
|
349
|
-
while (repairPassCount < maxRepairPass) {
|
|
350
|
-
const pass = runBrokenRefRepairPass(children, scanState, metrics)
|
|
351
|
-
hasBracketText = pass.hasBracketText
|
|
352
|
-
hasEmphasis = pass.hasEmphasis
|
|
353
|
-
hasLinkClose = pass.hasLinkClose
|
|
354
|
-
if (!pass.didRepair) break
|
|
355
|
-
changed = true
|
|
356
|
-
repairPassCount++
|
|
141
|
+
const createInlineChangeMarker = (facts) => {
|
|
142
|
+
return (startIdx) => {
|
|
143
|
+
markInlineLevelRebuildFrom(facts, startIdx)
|
|
357
144
|
}
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const finalizeInlineLinkRepairStage = (children, facts, markChangedFrom) => {
|
|
148
|
+
invalidateInlineDerivedCaches(facts)
|
|
149
|
+
if (!mergeBrokenMarksAroundLinks(children, markChangedFrom)) return false
|
|
150
|
+
invalidateInlineDerivedCaches(facts)
|
|
151
|
+
rebuildInlineLevelsForFacts(children, facts)
|
|
152
|
+
return true
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const BROKEN_REF_REPAIR_HOOKS = {
|
|
156
|
+
ensureLinkCloseMap: ensureInlineLinkCloseMap,
|
|
157
|
+
ensureWrapperPrefixStats: ensureInlineWrapperPrefixStats,
|
|
158
|
+
invalidateDerivedCaches: invalidateInlineDerivedCaches,
|
|
159
|
+
markLevelRebuildFrom: markInlineLevelRebuildFrom
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
const bumpPostprocessMetric = (metrics, bucket, key) => {
|
|
163
|
+
if (!metrics || !bucket || !key) return
|
|
164
|
+
let table = metrics[bucket]
|
|
165
|
+
if (!table || typeof table !== 'object') {
|
|
166
|
+
table = Object.create(null)
|
|
167
|
+
metrics[bucket] = table
|
|
363
168
|
}
|
|
169
|
+
table[key] = (table[key] || 0) + 1
|
|
364
170
|
}
|
|
365
171
|
|
|
366
172
|
const scanTailRepairCandidateAfterLinkClose = (tokens, linkCloseIdx) => {
|
|
@@ -388,7 +194,7 @@ const scanTailRepairCandidateAfterLinkClose = (tokens, linkCloseIdx) => {
|
|
|
388
194
|
return { startIdx, strongCloseIdx: foundStrongClose }
|
|
389
195
|
}
|
|
390
196
|
|
|
391
|
-
const tryRepairTailCandidate = (tokens, candidate, isJapaneseMode, metrics = null) => {
|
|
197
|
+
const tryRepairTailCandidate = (tokens, candidate, isJapaneseMode, metrics = null, onChangeStart = null) => {
|
|
392
198
|
if (!tokens || !candidate) return false
|
|
393
199
|
const startIdx = candidate.startIdx
|
|
394
200
|
const strongCloseIdx = candidate.strongCloseIdx
|
|
@@ -396,17 +202,19 @@ const tryRepairTailCandidate = (tokens, candidate, isJapaneseMode, metrics = nul
|
|
|
396
202
|
if (isJapaneseMode && !hasJapaneseContextInRange(tokens, startIdx, endIdx)) return false
|
|
397
203
|
if (!hasEmphasisSignalInRange(tokens, startIdx, endIdx)) return false
|
|
398
204
|
if (tryFixTailPatternTokenOnly(tokens, startIdx, endIdx)) {
|
|
205
|
+
if (onChangeStart) onChangeStart(startIdx)
|
|
399
206
|
bumpPostprocessMetric(metrics, 'tailFastPaths', 'tail-pattern')
|
|
400
207
|
return true
|
|
401
208
|
}
|
|
402
209
|
if (tryFixTailDanglingStrongCloseTokenOnly(tokens, startIdx, strongCloseIdx)) {
|
|
210
|
+
if (onChangeStart) onChangeStart(startIdx)
|
|
403
211
|
bumpPostprocessMetric(metrics, 'tailFastPaths', 'tail-dangling-strong-close')
|
|
404
212
|
return true
|
|
405
213
|
}
|
|
406
214
|
return false
|
|
407
215
|
}
|
|
408
216
|
|
|
409
|
-
const fixTailAfterLinkStrongClose = (tokens, isJapaneseMode, metrics = null) => {
|
|
217
|
+
const fixTailAfterLinkStrongClose = (tokens, isJapaneseMode, metrics = null, onChangeStart = null) => {
|
|
410
218
|
let strongDepth = 0
|
|
411
219
|
for (let i = 0; i < tokens.length; i++) {
|
|
412
220
|
const t = tokens[i]
|
|
@@ -419,7 +227,7 @@ const fixTailAfterLinkStrongClose = (tokens, isJapaneseMode, metrics = null) =>
|
|
|
419
227
|
if (strongDepth !== 0) continue
|
|
420
228
|
const candidate = scanTailRepairCandidateAfterLinkClose(tokens, i)
|
|
421
229
|
if (!candidate) continue
|
|
422
|
-
if (tryRepairTailCandidate(tokens, candidate, isJapaneseMode, metrics)) return true
|
|
230
|
+
if (tryRepairTailCandidate(tokens, candidate, isJapaneseMode, metrics, onChangeStart)) return true
|
|
423
231
|
}
|
|
424
232
|
return false
|
|
425
233
|
}
|
|
@@ -441,6 +249,9 @@ const isSoftSpaceCode = (code) => {
|
|
|
441
249
|
return code === 0x20 || code === 0x09 || code === 0x3000
|
|
442
250
|
}
|
|
443
251
|
|
|
252
|
+
const CHAR_ASTERISK = 0x2A // *
|
|
253
|
+
const CHAR_BACKSLASH = 0x5C // \
|
|
254
|
+
|
|
444
255
|
const isAsciiWordCode = (code) => {
|
|
445
256
|
return (code >= 0x30 && code <= 0x39) ||
|
|
446
257
|
(code >= 0x41 && code <= 0x5A) ||
|
|
@@ -459,7 +270,7 @@ const textStartsAsciiWord = (text) => {
|
|
|
459
270
|
|
|
460
271
|
const isEscapedMarkerAt = (content, index) => {
|
|
461
272
|
let slashCount = 0
|
|
462
|
-
for (let i = index - 1; i >= 0 && content.charCodeAt(i) ===
|
|
273
|
+
for (let i = index - 1; i >= 0 && content.charCodeAt(i) === CHAR_BACKSLASH; i--) {
|
|
463
274
|
slashCount++
|
|
464
275
|
}
|
|
465
276
|
return (slashCount % 2) === 1
|
|
@@ -467,37 +278,34 @@ const isEscapedMarkerAt = (content, index) => {
|
|
|
467
278
|
|
|
468
279
|
const findLastStandaloneStrongMarker = (content) => {
|
|
469
280
|
if (!content || content.length < 2) return -1
|
|
470
|
-
let pos = content.
|
|
471
|
-
|
|
281
|
+
for (let pos = content.length - 2; pos >= 0; pos--) {
|
|
282
|
+
if (content.charCodeAt(pos) !== CHAR_ASTERISK ||
|
|
283
|
+
content.charCodeAt(pos + 1) !== CHAR_ASTERISK) {
|
|
284
|
+
continue
|
|
285
|
+
}
|
|
472
286
|
const prev = pos > 0 ? content.charCodeAt(pos - 1) : 0
|
|
473
287
|
const next = pos + 2 < content.length ? content.charCodeAt(pos + 2) : 0
|
|
474
|
-
if (prev
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
return pos
|
|
478
|
-
}
|
|
479
|
-
pos = content.lastIndexOf('**', pos - 1)
|
|
288
|
+
if (prev === CHAR_ASTERISK || next === CHAR_ASTERISK) continue
|
|
289
|
+
if (prev === CHAR_BACKSLASH && isEscapedMarkerAt(content, pos)) continue
|
|
290
|
+
return pos
|
|
480
291
|
}
|
|
481
292
|
return -1
|
|
482
293
|
}
|
|
483
294
|
|
|
484
295
|
const hasLeadingStandaloneStrongMarker = (content) => {
|
|
485
296
|
if (!content || content.length < 2) return false
|
|
486
|
-
if (content.charCodeAt(0) !==
|
|
487
|
-
if (content.length > 2 && content.charCodeAt(2) ===
|
|
297
|
+
if (content.charCodeAt(0) !== CHAR_ASTERISK || content.charCodeAt(1) !== CHAR_ASTERISK) return false
|
|
298
|
+
if (content.length > 2 && content.charCodeAt(2) === CHAR_ASTERISK) return false
|
|
488
299
|
return true
|
|
489
300
|
}
|
|
490
301
|
|
|
491
|
-
const tryPromoteStrongAroundInlineLink = (tokens, strictAsciiStrongGuard = false) => {
|
|
302
|
+
const tryPromoteStrongAroundInlineLink = (tokens, strictAsciiStrongGuard = false, facts = null) => {
|
|
492
303
|
if (!tokens || tokens.length < 3) return false
|
|
493
304
|
let changed = false
|
|
494
|
-
let linkCloseMap = null
|
|
495
305
|
for (let i = 1; i < tokens.length - 1; i++) {
|
|
496
306
|
const linkOpen = tokens[i]
|
|
497
307
|
if (!linkOpen || linkOpen.type !== 'link_open') continue
|
|
498
|
-
|
|
499
|
-
linkCloseMap = buildLinkCloseMap(tokens, 0, tokens.length - 1)
|
|
500
|
-
}
|
|
308
|
+
const linkCloseMap = ensureInlineLinkCloseMap(facts, tokens)
|
|
501
309
|
const closeIdx = linkCloseMap.get(i) ?? -1
|
|
502
310
|
if (closeIdx === -1 || closeIdx + 1 >= tokens.length) continue
|
|
503
311
|
|
|
@@ -554,8 +362,8 @@ const tryPromoteStrongAroundInlineLink = (tokens, strictAsciiStrongGuard = false
|
|
|
554
362
|
|
|
555
363
|
tokens.splice(i - 1, closeIdx - i + 3, ...replacement)
|
|
556
364
|
changed = true
|
|
557
|
-
|
|
558
|
-
|
|
365
|
+
invalidateInlineDerivedCaches(facts)
|
|
366
|
+
markInlineLevelRebuildFrom(facts, i - 1)
|
|
559
367
|
i = i - 1 + replacement.length - 1
|
|
560
368
|
}
|
|
561
369
|
return changed
|
|
@@ -564,7 +372,8 @@ const tryPromoteStrongAroundInlineLink = (tokens, strictAsciiStrongGuard = false
|
|
|
564
372
|
const tryPromoteStrongAroundInlineCode = (
|
|
565
373
|
tokens,
|
|
566
374
|
strictAsciiCodeGuard = false,
|
|
567
|
-
strictAsciiStrongGuard = false
|
|
375
|
+
strictAsciiStrongGuard = false,
|
|
376
|
+
facts = null
|
|
568
377
|
) => {
|
|
569
378
|
if (!tokens || tokens.length < 3) return false
|
|
570
379
|
let changed = false
|
|
@@ -615,82 +424,200 @@ const tryPromoteStrongAroundInlineCode = (
|
|
|
615
424
|
|
|
616
425
|
tokens.splice(i, 3, ...replacement)
|
|
617
426
|
changed = true
|
|
427
|
+
invalidateInlineDerivedCaches(facts)
|
|
428
|
+
markInlineLevelRebuildFrom(facts, i)
|
|
618
429
|
i += replacement.length - 1
|
|
619
430
|
}
|
|
620
431
|
return changed
|
|
621
432
|
}
|
|
622
433
|
|
|
434
|
+
const tryActivateInlineEmphasis = (
|
|
435
|
+
children,
|
|
436
|
+
facts,
|
|
437
|
+
strictAsciiCodeGuard,
|
|
438
|
+
strictAsciiStrongGuard
|
|
439
|
+
) => {
|
|
440
|
+
if (!facts || facts.hasEmphasis) return false
|
|
441
|
+
if (facts.hasLinkOpen &&
|
|
442
|
+
facts.hasLinkClose &&
|
|
443
|
+
tryPromoteStrongAroundInlineLink(children, strictAsciiStrongGuard, facts)) {
|
|
444
|
+
facts.hasEmphasis = true
|
|
445
|
+
return true
|
|
446
|
+
}
|
|
447
|
+
if (facts.hasBracketText || facts.hasLinkOpen || facts.hasLinkClose) return false
|
|
448
|
+
if (!facts.hasCodeInline) return false
|
|
449
|
+
if (tryPromoteStrongAroundInlineCode(children, strictAsciiCodeGuard, strictAsciiStrongGuard, facts)) {
|
|
450
|
+
facts.hasEmphasis = true
|
|
451
|
+
return true
|
|
452
|
+
}
|
|
453
|
+
return false
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
const shouldRunInlineBrokenRefRepair = (facts, inlineContent, state) => {
|
|
457
|
+
if (!facts || !facts.hasLinkOpen || !facts.hasLinkClose || !facts.hasBracketText) return false
|
|
458
|
+
if (inlineContent.indexOf('***') !== -1) return false
|
|
459
|
+
return getReferenceCount(state) > 0
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
const applyBrokenRefRepairFacts = (facts, repairs) => {
|
|
463
|
+
if (!facts || !repairs) return
|
|
464
|
+
facts.hasBracketText = repairs.hasBracketText
|
|
465
|
+
facts.hasEmphasis = repairs.hasEmphasis
|
|
466
|
+
facts.hasLinkClose = repairs.hasLinkClose
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
const createBrokenRefScanState = () => {
|
|
470
|
+
return { depth: 0, brokenEnd: false, tailOpen: -1 }
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
const runInlineBrokenRefRepairStage = (children, facts, inlineContent, state) => {
|
|
474
|
+
if (!shouldRunInlineBrokenRefRepair(facts, inlineContent, state)) return false
|
|
475
|
+
const scanState = createBrokenRefScanState()
|
|
476
|
+
const maxRepairPass = computeMaxBrokenRefRepairPass(children, scanState)
|
|
477
|
+
if (maxRepairPass <= 0) return false
|
|
478
|
+
const repairs = runBrokenRefRepairs(
|
|
479
|
+
children,
|
|
480
|
+
maxRepairPass,
|
|
481
|
+
scanState,
|
|
482
|
+
getPostprocessMetrics(state),
|
|
483
|
+
facts,
|
|
484
|
+
BROKEN_REF_REPAIR_HOOKS
|
|
485
|
+
)
|
|
486
|
+
applyBrokenRefRepairFacts(facts, repairs)
|
|
487
|
+
return repairs.changed
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
const runInlineEmphasisRepairStage = (children, facts, state, isJapaneseMode) => {
|
|
491
|
+
if (!facts.hasEmphasis) return false
|
|
492
|
+
let changed = false
|
|
493
|
+
const markChangedFrom = createInlineChangeMarker(facts)
|
|
494
|
+
if (fixEmOuterStrongSequence(children, markChangedFrom)) changed = true
|
|
495
|
+
if (facts.hasLinkClose) {
|
|
496
|
+
const metrics = getPostprocessMetrics(state)
|
|
497
|
+
if (fixTailAfterLinkStrongClose(children, isJapaneseMode, metrics, markChangedFrom)) changed = true
|
|
498
|
+
if (fixLeadingAsteriskEm(children, markChangedFrom)) changed = true
|
|
499
|
+
}
|
|
500
|
+
if (fixTrailingStrong(children, markChangedFrom)) changed = true
|
|
501
|
+
if (sanitizeEmStrongBalance(children, markChangedFrom)) changed = true
|
|
502
|
+
return changed
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
const shouldRunInlineCollapsedRefRepair = (facts, state) => {
|
|
506
|
+
if (!facts || !facts.hasBracketText) return false
|
|
507
|
+
return getReferenceCount(state) > 0
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
const applyCollapsedRefRepairFacts = (facts) => {
|
|
511
|
+
if (!facts) return
|
|
512
|
+
facts.hasLinkOpen = true
|
|
513
|
+
facts.hasLinkClose = true
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
const rewriteInlineCollapsedReferences = (children, facts, state, markChangedFrom) => {
|
|
517
|
+
const changed = convertCollapsedReferenceLinks(
|
|
518
|
+
children,
|
|
519
|
+
state,
|
|
520
|
+
facts,
|
|
521
|
+
markChangedFrom
|
|
522
|
+
)
|
|
523
|
+
if (!changed) return false
|
|
524
|
+
applyCollapsedRefRepairFacts(facts)
|
|
525
|
+
return true
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const runInlineCollapsedRefStage = (children, facts, state) => {
|
|
529
|
+
if (!shouldRunInlineCollapsedRefRepair(facts, state)) return false
|
|
530
|
+
const markChangedFrom = createInlineChangeMarker(facts)
|
|
531
|
+
if (!rewriteInlineCollapsedReferences(children, facts, state, markChangedFrom)) return false
|
|
532
|
+
finalizeInlineLinkRepairStage(children, facts, markChangedFrom)
|
|
533
|
+
return true
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
const shouldSkipInlinePostprocessToken = (children, facts, isJapaneseMode) => {
|
|
537
|
+
if (!facts.hasEmphasis &&
|
|
538
|
+
!facts.hasBracketText &&
|
|
539
|
+
!facts.hasLinkOpen &&
|
|
540
|
+
!facts.hasLinkClose &&
|
|
541
|
+
!facts.hasCodeInline) {
|
|
542
|
+
return true
|
|
543
|
+
}
|
|
544
|
+
if (isJapaneseMode &&
|
|
545
|
+
!hasJapaneseContextInRange(children, 0, children.length - 1)) {
|
|
546
|
+
return true
|
|
547
|
+
}
|
|
548
|
+
return false
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
const runInlineCoreRepairStages = (
|
|
552
|
+
children,
|
|
553
|
+
facts,
|
|
554
|
+
inlineContent,
|
|
555
|
+
state,
|
|
556
|
+
isJapaneseMode,
|
|
557
|
+
strictAsciiCodeGuard,
|
|
558
|
+
strictAsciiStrongGuard
|
|
559
|
+
) => {
|
|
560
|
+
let changed = false
|
|
561
|
+
if (!facts.hasEmphasis && tryActivateInlineEmphasis(
|
|
562
|
+
children,
|
|
563
|
+
facts,
|
|
564
|
+
strictAsciiCodeGuard,
|
|
565
|
+
strictAsciiStrongGuard
|
|
566
|
+
)) {
|
|
567
|
+
changed = true
|
|
568
|
+
} else if (!facts.hasEmphasis && !facts.hasBracketText) {
|
|
569
|
+
return false
|
|
570
|
+
}
|
|
571
|
+
if (runInlineBrokenRefRepairStage(children, facts, inlineContent, state)) changed = true
|
|
572
|
+
if (runInlineEmphasisRepairStage(children, facts, state, isJapaneseMode)) changed = true
|
|
573
|
+
return changed
|
|
574
|
+
}
|
|
575
|
+
|
|
623
576
|
const processInlinePostprocessToken = (
|
|
624
577
|
token,
|
|
625
578
|
inlineContent,
|
|
626
579
|
state,
|
|
627
580
|
isJapaneseMode,
|
|
628
581
|
strictAsciiCodeGuard,
|
|
629
|
-
strictAsciiStrongGuard
|
|
630
|
-
referenceCount,
|
|
631
|
-
metrics = null
|
|
582
|
+
strictAsciiStrongGuard
|
|
632
583
|
) => {
|
|
633
584
|
if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) return
|
|
634
585
|
const children = token.children
|
|
635
|
-
const
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
shouldTryBrokenRefRepair = false
|
|
670
|
-
}
|
|
671
|
-
if (shouldTryBrokenRefRepair) {
|
|
672
|
-
const scanState = { depth: 0, brokenEnd: false, tailOpen: -1 }
|
|
673
|
-
const maxRepairPass = computeMaxBrokenRefRepairPass(children, scanState)
|
|
674
|
-
if (maxRepairPass > 0) {
|
|
675
|
-
const repairs = runBrokenRefRepairs(children, maxRepairPass, scanState, metrics)
|
|
676
|
-
hasBracketText = repairs.hasBracketText
|
|
677
|
-
hasEmphasis = repairs.hasEmphasis
|
|
678
|
-
hasLinkClose = repairs.hasLinkClose
|
|
679
|
-
if (repairs.changed) changed = true
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
if (hasEmphasis) {
|
|
683
|
-
if (fixEmOuterStrongSequence(children)) changed = true
|
|
684
|
-
if (hasLinkClose && fixTailAfterLinkStrongClose(children, isJapaneseMode, metrics)) changed = true
|
|
685
|
-
if (hasLinkClose && fixLeadingAsteriskEm(children)) changed = true
|
|
686
|
-
if (fixTrailingStrong(children)) changed = true
|
|
687
|
-
if (sanitizeEmStrongBalance(children)) changed = true
|
|
586
|
+
const facts = buildInlinePostprocessFacts(children, inlineContent)
|
|
587
|
+
if (shouldSkipInlinePostprocessToken(children, facts, isJapaneseMode)) return
|
|
588
|
+
const changed = runInlineCoreRepairStages(
|
|
589
|
+
children,
|
|
590
|
+
facts,
|
|
591
|
+
inlineContent,
|
|
592
|
+
state,
|
|
593
|
+
isJapaneseMode,
|
|
594
|
+
strictAsciiCodeGuard,
|
|
595
|
+
strictAsciiStrongGuard
|
|
596
|
+
)
|
|
597
|
+
if (changed) rebuildInlineLevelsForFacts(children, facts)
|
|
598
|
+
runInlineCollapsedRefStage(children, facts, state)
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
const processInlinePostprocessStateTokens = (
|
|
602
|
+
state,
|
|
603
|
+
isJapaneseMode,
|
|
604
|
+
strictAsciiCodeGuard,
|
|
605
|
+
strictAsciiStrongGuard
|
|
606
|
+
) => {
|
|
607
|
+
for (let i = 0; i < state.tokens.length; i++) {
|
|
608
|
+
const token = state.tokens[i]
|
|
609
|
+
if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
|
|
610
|
+
const inlineContent = typeof token.content === 'string' ? token.content : ''
|
|
611
|
+
if (!hasMarkerChars(inlineContent)) continue
|
|
612
|
+
processInlinePostprocessToken(
|
|
613
|
+
token,
|
|
614
|
+
inlineContent,
|
|
615
|
+
state,
|
|
616
|
+
isJapaneseMode,
|
|
617
|
+
strictAsciiCodeGuard,
|
|
618
|
+
strictAsciiStrongGuard
|
|
619
|
+
)
|
|
688
620
|
}
|
|
689
|
-
if (changed) rebuildInlineLevels(children)
|
|
690
|
-
if (!hasBracketText) return
|
|
691
|
-
if (referenceCount > 0) convertCollapsedReferenceLinks(children, state)
|
|
692
|
-
if (referenceCount === 0 && !hasLinkClose) return
|
|
693
|
-
mergeBrokenMarksAroundLinks(children)
|
|
694
621
|
}
|
|
695
622
|
|
|
696
623
|
const registerTokenPostprocess = (md, baseOpt) => {
|
|
@@ -698,35 +625,18 @@ const registerTokenPostprocess = (md, baseOpt) => {
|
|
|
698
625
|
md.__strongJaTokenPostprocessRegistered = true
|
|
699
626
|
md.core.ruler.after('inline', 'strong_ja_token_postprocess', (state) => {
|
|
700
627
|
if (!state || !state.tokens) return
|
|
701
|
-
const
|
|
702
|
-
const
|
|
703
|
-
|
|
704
|
-
const
|
|
705
|
-
const
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
const metrics = getPostprocessMetrics(state)
|
|
714
|
-
for (let i = 0; i < state.tokens.length; i++) {
|
|
715
|
-
const token = state.tokens[i]
|
|
716
|
-
if (!token || token.type !== 'inline' || !token.children || token.children.length === 0) continue
|
|
717
|
-
const inlineContent = typeof token.content === 'string' ? token.content : ''
|
|
718
|
-
if (!hasMarkerChars(inlineContent)) continue
|
|
719
|
-
processInlinePostprocessToken(
|
|
720
|
-
token,
|
|
721
|
-
inlineContent,
|
|
722
|
-
state,
|
|
723
|
-
isJapaneseMode,
|
|
724
|
-
strictAsciiCodeGuard,
|
|
725
|
-
strictAsciiStrongGuard,
|
|
726
|
-
referenceCount,
|
|
727
|
-
metrics
|
|
728
|
-
)
|
|
729
|
-
}
|
|
628
|
+
const overrideOpt = state.env && state.env.__strongJaTokenOpt
|
|
629
|
+
const opt = hasRuntimeOverride(overrideOpt) ? getRuntimeOpt(state, baseOpt) : baseOpt
|
|
630
|
+
if (!opt.__strongJaPostprocessActive) return
|
|
631
|
+
const isJapaneseMode = opt.__strongJaIsJapaneseMode
|
|
632
|
+
const strictAsciiCodeGuard = opt.__strongJaStrictAsciiCodeGuard
|
|
633
|
+
const strictAsciiStrongGuard = opt.__strongJaStrictAsciiStrongGuard
|
|
634
|
+
processInlinePostprocessStateTokens(
|
|
635
|
+
state,
|
|
636
|
+
isJapaneseMode,
|
|
637
|
+
strictAsciiCodeGuard,
|
|
638
|
+
strictAsciiStrongGuard
|
|
639
|
+
)
|
|
730
640
|
})
|
|
731
641
|
}
|
|
732
642
|
|