| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241 |
- /**
- * @import {
- * Code,
- * Construct,
- * Event,
- * Point,
- * Resolver,
- * State,
- * TokenizeContext,
- * Tokenizer,
- * Token
- * } from 'micromark-util-types'
- */
- import { push, splice } from 'micromark-util-chunked';
- import { classifyCharacter } from 'micromark-util-classify-character';
- import { resolveAll } from 'micromark-util-resolve-all';
- /** @type {Construct} */
- export const attention = {
- name: 'attention',
- resolveAll: resolveAllAttention,
- tokenize: tokenizeAttention
- };
- /**
- * Take all events and resolve attention to emphasis or strong.
- *
- * @type {Resolver}
- */
- // eslint-disable-next-line complexity
- function resolveAllAttention(events, context) {
- let index = -1;
- /** @type {number} */
- let open;
- /** @type {Token} */
- let group;
- /** @type {Token} */
- let text;
- /** @type {Token} */
- let openingSequence;
- /** @type {Token} */
- let closingSequence;
- /** @type {number} */
- let use;
- /** @type {Array<Event>} */
- let nextEvents;
- /** @type {number} */
- let offset;
- // Walk through all events.
- //
- // Note: performance of this is fine on an mb of normal markdown, but it’s
- // a bottleneck for malicious stuff.
- while (++index < events.length) {
- // Find a token that can close.
- if (events[index][0] === 'enter' && events[index][1].type === 'attentionSequence' && events[index][1]._close) {
- open = index;
- // Now walk back to find an opener.
- while (open--) {
- // Find a token that can open the closer.
- if (events[open][0] === 'exit' && events[open][1].type === 'attentionSequence' && events[open][1]._open &&
- // If the markers are the same:
- context.sliceSerialize(events[open][1]).charCodeAt(0) === context.sliceSerialize(events[index][1]).charCodeAt(0)) {
- // If the opening can close or the closing can open,
- // and the close size *is not* a multiple of three,
- // but the sum of the opening and closing size *is* multiple of three,
- // then don’t match.
- if ((events[open][1]._close || events[index][1]._open) && (events[index][1].end.offset - events[index][1].start.offset) % 3 && !((events[open][1].end.offset - events[open][1].start.offset + events[index][1].end.offset - events[index][1].start.offset) % 3)) {
- continue;
- }
- // Number of markers to use from the sequence.
- use = events[open][1].end.offset - events[open][1].start.offset > 1 && events[index][1].end.offset - events[index][1].start.offset > 1 ? 2 : 1;
- const start = {
- ...events[open][1].end
- };
- const end = {
- ...events[index][1].start
- };
- movePoint(start, -use);
- movePoint(end, use);
- openingSequence = {
- type: use > 1 ? "strongSequence" : "emphasisSequence",
- start,
- end: {
- ...events[open][1].end
- }
- };
- closingSequence = {
- type: use > 1 ? "strongSequence" : "emphasisSequence",
- start: {
- ...events[index][1].start
- },
- end
- };
- text = {
- type: use > 1 ? "strongText" : "emphasisText",
- start: {
- ...events[open][1].end
- },
- end: {
- ...events[index][1].start
- }
- };
- group = {
- type: use > 1 ? "strong" : "emphasis",
- start: {
- ...openingSequence.start
- },
- end: {
- ...closingSequence.end
- }
- };
- events[open][1].end = {
- ...openingSequence.start
- };
- events[index][1].start = {
- ...closingSequence.end
- };
- nextEvents = [];
- // If there are more markers in the opening, add them before.
- if (events[open][1].end.offset - events[open][1].start.offset) {
- nextEvents = push(nextEvents, [['enter', events[open][1], context], ['exit', events[open][1], context]]);
- }
- // Opening.
- nextEvents = push(nextEvents, [['enter', group, context], ['enter', openingSequence, context], ['exit', openingSequence, context], ['enter', text, context]]);
- // Always populated by defaults.
- // Between.
- nextEvents = push(nextEvents, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open + 1, index), context));
- // Closing.
- nextEvents = push(nextEvents, [['exit', text, context], ['enter', closingSequence, context], ['exit', closingSequence, context], ['exit', group, context]]);
- // If there are more markers in the closing, add them after.
- if (events[index][1].end.offset - events[index][1].start.offset) {
- offset = 2;
- nextEvents = push(nextEvents, [['enter', events[index][1], context], ['exit', events[index][1], context]]);
- } else {
- offset = 0;
- }
- splice(events, open - 1, index - open + 3, nextEvents);
- index = open + nextEvents.length - offset - 2;
- break;
- }
- }
- }
- }
- // Remove remaining sequences.
- index = -1;
- while (++index < events.length) {
- if (events[index][1].type === 'attentionSequence') {
- events[index][1].type = 'data';
- }
- }
- return events;
- }
- /**
- * @this {TokenizeContext}
- * Context.
- * @type {Tokenizer}
- */
- function tokenizeAttention(effects, ok) {
- const attentionMarkers = this.parser.constructs.attentionMarkers.null;
- const previous = this.previous;
- const before = classifyCharacter(previous);
- /** @type {NonNullable<Code>} */
- let marker;
- return start;
- /**
- * Before a sequence.
- *
- * ```markdown
- * > | **
- * ^
- * ```
- *
- * @type {State}
- */
- function start(code) {
- marker = code;
- effects.enter('attentionSequence');
- return inside(code);
- }
- /**
- * In a sequence.
- *
- * ```markdown
- * > | **
- * ^^
- * ```
- *
- * @type {State}
- */
- function inside(code) {
- if (code === marker) {
- effects.consume(code);
- return inside;
- }
- const token = effects.exit('attentionSequence');
- // To do: next major: move this to resolver, just like `markdown-rs`.
- const after = classifyCharacter(code);
- // Always populated by defaults.
- const open = !after || after === 2 && before || attentionMarkers.includes(code);
- const close = !before || before === 2 && after || attentionMarkers.includes(previous);
- token._open = Boolean(marker === 42 ? open : open && (before || !close));
- token._close = Boolean(marker === 42 ? close : close && (after || !open));
- return ok(code);
- }
- }
- /**
- * Move a point a bit.
- *
- * Note: `move` only works inside lines! It’s not possible to move past other
- * chunks (replacement characters, tabs, or line endings).
- *
- * @param {Point} point
- * Point.
- * @param {number} offset
- * Amount to move.
- * @returns {undefined}
- * Nothing.
- */
- function movePoint(point, offset) {
- point.column += offset;
- point.offset += offset;
- point._bufferIndex += offset;
- }
|