File size: 4,517 Bytes
bc20498 |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 |
/**
* @typedef {import('micromark-util-types').Code} Code
* @typedef {import('micromark-util-types').Construct} Construct
* @typedef {import('micromark-util-types').Resolver} Resolver
* @typedef {import('micromark-util-types').State} State
* @typedef {import('micromark-util-types').TokenizeContext} TokenizeContext
* @typedef {import('micromark-util-types').Tokenizer} Tokenizer
*/
import {factorySpace} from 'micromark-factory-space'
import {markdownLineEnding, markdownSpace} from 'micromark-util-character'
/** @type {Construct} */
export const setextUnderline = {
name: 'setextUnderline',
tokenize: tokenizeSetextUnderline,
resolveTo: resolveToSetextUnderline
}
/** @type {Resolver} */
function resolveToSetextUnderline(events, context) {
// To do: resolve like `markdown-rs`.
let index = events.length
/** @type {number | undefined} */
let content
/** @type {number | undefined} */
let text
/** @type {number | undefined} */
let definition
// Find the opening of the content.
// It’ll always exist: we don’t tokenize if it isn’t there.
while (index--) {
if (events[index][0] === 'enter') {
if (events[index][1].type === 'content') {
content = index
break
}
if (events[index][1].type === 'paragraph') {
text = index
}
}
// Exit
else {
if (events[index][1].type === 'content') {
// Remove the content end (if needed we’ll add it later)
events.splice(index, 1)
}
if (!definition && events[index][1].type === 'definition') {
definition = index
}
}
}
const heading = {
type: 'setextHeading',
start: Object.assign({}, events[text][1].start),
end: Object.assign({}, events[events.length - 1][1].end)
}
// Change the paragraph to setext heading text.
events[text][1].type = 'setextHeadingText'
// If we have definitions in the content, we’ll keep on having content,
// but we need move it.
if (definition) {
events.splice(text, 0, ['enter', heading, context])
events.splice(definition + 1, 0, ['exit', events[content][1], context])
events[content][1].end = Object.assign({}, events[definition][1].end)
} else {
events[content][1] = heading
}
// Add the heading exit at the end.
events.push(['exit', heading, context])
return events
}
/**
* @this {TokenizeContext}
* @type {Tokenizer}
*/
function tokenizeSetextUnderline(effects, ok, nok) {
const self = this
/** @type {NonNullable<Code>} */
let marker
return start
/**
* At start of heading (setext) underline.
*
* ```markdown
* | aa
* > | ==
* ^
* ```
*
* @type {State}
*/
function start(code) {
let index = self.events.length
/** @type {boolean | undefined} */
let paragraph
// Find an opening.
while (index--) {
// Skip enter/exit of line ending, line prefix, and content.
// We can now either have a definition or a paragraph.
if (
self.events[index][1].type !== 'lineEnding' &&
self.events[index][1].type !== 'linePrefix' &&
self.events[index][1].type !== 'content'
) {
paragraph = self.events[index][1].type === 'paragraph'
break
}
}
// To do: handle lazy/pierce like `markdown-rs`.
// To do: parse indent like `markdown-rs`.
if (!self.parser.lazy[self.now().line] && (self.interrupt || paragraph)) {
effects.enter('setextHeadingLine')
marker = code
return before(code)
}
return nok(code)
}
/**
* After optional whitespace, at `-` or `=`.
*
* ```markdown
* | aa
* > | ==
* ^
* ```
*
* @type {State}
*/
function before(code) {
effects.enter('setextHeadingLineSequence')
return inside(code)
}
/**
* In sequence.
*
* ```markdown
* | aa
* > | ==
* ^
* ```
*
* @type {State}
*/
function inside(code) {
if (code === marker) {
effects.consume(code)
return inside
}
effects.exit('setextHeadingLineSequence')
return markdownSpace(code)
? factorySpace(effects, after, 'lineSuffix')(code)
: after(code)
}
/**
* After sequence, after optional whitespace.
*
* ```markdown
* | aa
* > | ==
* ^
* ```
*
* @type {State}
*/
function after(code) {
if (code === null || markdownLineEnding(code)) {
effects.exit('setextHeadingLine')
return ok(code)
}
return nok(code)
}
}
|