155 lines
4.5 KiB
JavaScript
155 lines
4.5 KiB
JavaScript
|
|
// Inline parser state
|
||
|
|
|
||
|
|
import Token from '../token.mjs'
|
||
|
|
import { isWhiteSpace, isPunctCharCode, isMdAsciiPunct } from '../common/utils.mjs'
|
||
|
|
|
||
|
|
function StateInline (src, md, env, outTokens) {
|
||
|
|
this.src = src
|
||
|
|
this.env = env
|
||
|
|
this.md = md
|
||
|
|
this.tokens = outTokens
|
||
|
|
this.tokens_meta = Array(outTokens.length)
|
||
|
|
|
||
|
|
this.pos = 0
|
||
|
|
this.posMax = this.src.length
|
||
|
|
this.level = 0
|
||
|
|
this.pending = ''
|
||
|
|
this.pendingLevel = 0
|
||
|
|
|
||
|
|
// Stores { start: end } pairs. Useful for backtrack
|
||
|
|
// optimization of pairs parse (emphasis, strikes).
|
||
|
|
this.cache = {}
|
||
|
|
|
||
|
|
// List of emphasis-like delimiters for current tag
|
||
|
|
this.delimiters = []
|
||
|
|
|
||
|
|
// Stack of delimiter lists for upper level tags
|
||
|
|
this._prev_delimiters = []
|
||
|
|
|
||
|
|
// backtick length => last seen position
|
||
|
|
this.backticks = {}
|
||
|
|
this.backticksScanned = false
|
||
|
|
|
||
|
|
// Counter used to disable inline linkify-it execution
|
||
|
|
// inside <a> and markdown links
|
||
|
|
this.linkLevel = 0
|
||
|
|
}
|
||
|
|
|
||
|
|
// Flush pending text
|
||
|
|
//
|
||
|
|
StateInline.prototype.pushPending = function () {
|
||
|
|
const token = new Token('text', '', 0)
|
||
|
|
token.content = this.pending
|
||
|
|
token.level = this.pendingLevel
|
||
|
|
this.tokens.push(token)
|
||
|
|
this.pending = ''
|
||
|
|
return token
|
||
|
|
}
|
||
|
|
|
||
|
|
// Push new token to "stream".
|
||
|
|
// If pending text exists - flush it as text token
|
||
|
|
//
|
||
|
|
StateInline.prototype.push = function (type, tag, nesting) {
|
||
|
|
if (this.pending) {
|
||
|
|
this.pushPending()
|
||
|
|
}
|
||
|
|
|
||
|
|
const token = new Token(type, tag, nesting)
|
||
|
|
let token_meta = null
|
||
|
|
|
||
|
|
if (nesting < 0) {
|
||
|
|
// closing tag
|
||
|
|
this.level--
|
||
|
|
this.delimiters = this._prev_delimiters.pop()
|
||
|
|
}
|
||
|
|
|
||
|
|
token.level = this.level
|
||
|
|
|
||
|
|
if (nesting > 0) {
|
||
|
|
// opening tag
|
||
|
|
this.level++
|
||
|
|
this._prev_delimiters.push(this.delimiters)
|
||
|
|
this.delimiters = []
|
||
|
|
token_meta = { delimiters: this.delimiters }
|
||
|
|
}
|
||
|
|
|
||
|
|
this.pendingLevel = this.level
|
||
|
|
this.tokens.push(token)
|
||
|
|
this.tokens_meta.push(token_meta)
|
||
|
|
return token
|
||
|
|
}
|
||
|
|
|
||
|
|
// Scan a sequence of emphasis-like markers, and determine whether
|
||
|
|
// it can start an emphasis sequence or end an emphasis sequence.
|
||
|
|
//
|
||
|
|
// - start - position to scan from (it should point at a valid marker);
|
||
|
|
// - canSplitWord - determine if these markers can be found inside a word
|
||
|
|
//
|
||
|
|
StateInline.prototype.scanDelims = function (start, canSplitWord) {
|
||
|
|
const max = this.posMax
|
||
|
|
const marker = this.src.charCodeAt(start)
|
||
|
|
|
||
|
|
// Astral characters below are combined manually, because .codePointAt()
|
||
|
|
// does not guarantee numeric type output. And we don't wish JIT cache issues.
|
||
|
|
// The broken surrogate pairs are evaluated as U+FFFD to prevent possible
|
||
|
|
// crashes.
|
||
|
|
|
||
|
|
let lastChar
|
||
|
|
if (start === 0) {
|
||
|
|
// treat beginning of the line as a whitespace
|
||
|
|
lastChar = 0x20
|
||
|
|
} else if (start === 1) {
|
||
|
|
lastChar = this.src.charCodeAt(0)
|
||
|
|
if ((lastChar & 0xF800) === 0xD800) { lastChar = 0xFFFD }
|
||
|
|
} else {
|
||
|
|
lastChar = this.src.charCodeAt(start - 1)
|
||
|
|
if ((lastChar & 0xFC00) === 0xDC00) {
|
||
|
|
// low surrogate => add high one, replace broken pair with U+FFFD
|
||
|
|
const highSurr = this.src.charCodeAt(start - 2)
|
||
|
|
lastChar = (highSurr & 0xFC00) === 0xD800
|
||
|
|
? 0x10000 + ((highSurr - 0xD800) << 10) + (lastChar - 0xDC00)
|
||
|
|
: 0xFFFD
|
||
|
|
} else if ((lastChar & 0xFC00) === 0xD800) {
|
||
|
|
lastChar = 0xFFFD
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let pos = start
|
||
|
|
while (pos < max && this.src.charCodeAt(pos) === marker) { pos++ }
|
||
|
|
|
||
|
|
const count = pos - start
|
||
|
|
|
||
|
|
// treat end of the line as a whitespace
|
||
|
|
let nextChar = pos < max ? this.src.charCodeAt(pos) : 0x20
|
||
|
|
if ((nextChar & 0xFC00) === 0xD800) {
|
||
|
|
// high surrogate => add low one, replace broken pair with U+FFFD
|
||
|
|
const lowSurr = this.src.charCodeAt(pos + 1)
|
||
|
|
nextChar = (lowSurr & 0xFC00) === 0xDC00
|
||
|
|
? 0x10000 + ((nextChar - 0xD800) << 10) + (lowSurr - 0xDC00)
|
||
|
|
: 0xFFFD
|
||
|
|
} else if ((nextChar & 0xFC00) === 0xDC00) {
|
||
|
|
nextChar = 0xFFFD
|
||
|
|
}
|
||
|
|
|
||
|
|
const isLastPunctChar = isMdAsciiPunct(lastChar) || isPunctCharCode(lastChar)
|
||
|
|
const isNextPunctChar = isMdAsciiPunct(nextChar) || isPunctCharCode(nextChar)
|
||
|
|
|
||
|
|
const isLastWhiteSpace = isWhiteSpace(lastChar)
|
||
|
|
const isNextWhiteSpace = isWhiteSpace(nextChar)
|
||
|
|
|
||
|
|
const left_flanking =
|
||
|
|
!isNextWhiteSpace && (!isNextPunctChar || isLastWhiteSpace || isLastPunctChar)
|
||
|
|
const right_flanking =
|
||
|
|
!isLastWhiteSpace && (!isLastPunctChar || isNextWhiteSpace || isNextPunctChar)
|
||
|
|
|
||
|
|
const can_open = left_flanking && (canSplitWord || !right_flanking || isLastPunctChar)
|
||
|
|
const can_close = right_flanking && (canSplitWord || !left_flanking || isNextPunctChar)
|
||
|
|
|
||
|
|
return { can_open, can_close, length: count }
|
||
|
|
}
|
||
|
|
|
||
|
|
// re-export Token class to use in block rules
|
||
|
|
StateInline.prototype.Token = Token
|
||
|
|
|
||
|
|
export default StateInline
|