|
|
|
// For each opening emphasis-like marker find a matching closing one
|
|
|
|
//
|
|
|
|
|
|
|
|
function processDelimiters (delimiters) {
|
|
|
|
const openersBottom = {}
|
|
|
|
const max = delimiters.length
|
|
|
|
|
|
|
|
if (!max) return
|
|
|
|
|
|
|
|
// headerIdx is the first delimiter of the current (where closer is) delimiter run
|
|
|
|
let headerIdx = 0
|
|
|
|
let lastTokenIdx = -2 // needs any value lower than -1
|
|
|
|
const jumps = []
|
|
|
|
|
|
|
|
for (let closerIdx = 0; closerIdx < max; closerIdx++) {
|
|
|
|
const closer = delimiters[closerIdx]
|
|
|
|
|
|
|
|
jumps.push(0)
|
|
|
|
|
|
|
|
// markers belong to same delimiter run if:
|
|
|
|
// - they have adjacent tokens
|
|
|
|
// - AND markers are the same
|
|
|
|
//
|
|
|
|
if (delimiters[headerIdx].marker !== closer.marker || lastTokenIdx !== closer.token - 1) {
|
|
|
|
headerIdx = closerIdx
|
|
|
|
}
|
|
|
|
|
|
|
|
lastTokenIdx = closer.token
|
|
|
|
|
|
|
|
// Length is only used for emphasis-specific "rule of 3",
|
|
|
|
// if it's not defined (in strikethrough or 3rd party plugins),
|
|
|
|
// we can default it to 0 to disable those checks.
|
|
|
|
//
|
|
|
|
closer.length = closer.length || 0
|
|
|
|
|
|
|
|
if (!closer.close) continue
|
|
|
|
|
|
|
|
// Previously calculated lower bounds (previous fails)
|
|
|
|
// for each marker, each delimiter length modulo 3,
|
|
|
|
// and for whether this closer can be an opener;
|
|
|
|
// https://github.com/commonmark/cmark/commit/34250e12ccebdc6372b8b49c44fab57c72443460
|
|
|
|
/* eslint-disable-next-line no-prototype-builtins */
|
|
|
|
if (!openersBottom.hasOwnProperty(closer.marker)) {
|
|
|
|
openersBottom[closer.marker] = [-1, -1, -1, -1, -1, -1]
|
|
|
|
}
|
|
|
|
|
|
|
|
const minOpenerIdx = openersBottom[closer.marker][(closer.open ? 3 : 0) + (closer.length % 3)]
|
|
|
|
|
|
|
|
let openerIdx = headerIdx - jumps[headerIdx] - 1
|
|
|
|
|
|
|
|
let newMinOpenerIdx = openerIdx
|
|
|
|
|
|
|
|
for (; openerIdx > minOpenerIdx; openerIdx -= jumps[openerIdx] + 1) {
|
|
|
|
const opener = delimiters[openerIdx]
|
|
|
|
|
|
|
|
if (opener.marker !== closer.marker) continue
|
|
|
|
|
|
|
|
if (opener.open && opener.end < 0) {
|
|
|
|
let isOddMatch = false
|
|
|
|
|
|
|
|
// from spec:
|
|
|
|
//
|
|
|
|
// If one of the delimiters can both open and close emphasis, then the
|
|
|
|
// sum of the lengths of the delimiter runs containing the opening and
|
|
|
|
// closing delimiters must not be a multiple of 3 unless both lengths
|
|
|
|
// are multiples of 3.
|
|
|
|
//
|
|
|
|
if (opener.close || closer.open) {
|
|
|
|
if ((opener.length + closer.length) % 3 === 0) {
|
|
|
|
if (opener.length % 3 !== 0 || closer.length % 3 !== 0) {
|
|
|
|
isOddMatch = true
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!isOddMatch) {
|
|
|
|
// If previous delimiter cannot be an opener, we can safely skip
|
|
|
|
// the entire sequence in future checks. This is required to make
|
|
|
|
// sure algorithm has linear complexity (see *_*_*_*_*_... case).
|
|
|
|
//
|
|
|
|
const lastJump = openerIdx > 0 && !delimiters[openerIdx - 1].open
|
|
|
|
? jumps[openerIdx - 1] + 1
|
|
|
|
: 0
|
|
|
|
|
|
|
|
jumps[closerIdx] = closerIdx - openerIdx + lastJump
|
|
|
|
jumps[openerIdx] = lastJump
|
|
|
|
|
|
|
|
closer.open = false
|
|
|
|
opener.end = closerIdx
|
|
|
|
opener.close = false
|
|
|
|
newMinOpenerIdx = -1
|
|
|
|
// treat next token as start of run,
|
|
|
|
// it optimizes skips in **<...>**a**<...>** pathological case
|
|
|
|
lastTokenIdx = -2
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newMinOpenerIdx !== -1) {
|
|
|
|
// If match for this delimiter run failed, we want to set lower bound for
|
|
|
|
// future lookups. This is required to make sure algorithm has linear
|
|
|
|
// complexity.
|
|
|
|
//
|
|
|
|
// See details here:
|
|
|
|
// https://github.com/commonmark/cmark/issues/178#issuecomment-270417442
|
|
|
|
//
|
|
|
|
openersBottom[closer.marker][(closer.open ? 3 : 0) + ((closer.length || 0) % 3)] = newMinOpenerIdx
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
export default function link_pairs (state) {
|
|
|
|
const tokens_meta = state.tokens_meta
|
|
|
|
const max = state.tokens_meta.length
|
|
|
|
|
|
|
|
processDelimiters(state.delimiters)
|
|
|
|
|
|
|
|
for (let curr = 0; curr < max; curr++) {
|
|
|
|
if (tokens_meta[curr] && tokens_meta[curr].delimiters) {
|
|
|
|
processDelimiters(tokens_meta[curr].delimiters)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|