AK-21/Graphite-Industrial-Intelligence
0
1/**2 * @import {3 * Code,4 * Construct,5 * Event,6 * Point,7 * Resolver,8 * State,9 * TokenizeContext,10 * Tokenizer,11 * Token12 * } from 'micromark-util-types'13 */14 15import { push, splice } from 'micromark-util-chunked';16import { classifyCharacter } from 'micromark-util-classify-character';17import { resolveAll } from 'micromark-util-resolve-all';18/** @type {Construct} */19export const attention = {20 name: 'attention',21 resolveAll: resolveAllAttention,22 tokenize: tokenizeAttention23};24 25/**26 * Take all events and resolve attention to emphasis or strong.27 *28 * @type {Resolver}29 */30// eslint-disable-next-line complexity31function resolveAllAttention(events, context) {32 let index = -1;33 /** @type {number} */34 let open;35 /** @type {Token} */36 let group;37 /** @type {Token} */38 let text;39 /** @type {Token} */40 let openingSequence;41 /** @type {Token} */42 let closingSequence;43 /** @type {number} */44 let use;45 /** @type {Array<Event>} */46 let nextEvents;47 /** @type {number} */48 let offset;49 50 // Walk through all events.51 //52 // Note: performance of this is fine on an mb of normal markdown, but it’s53 // a bottleneck for malicious stuff.54 while (++index < events.length) {55 // Find a token that can close.56 if (events[index][0] === 'enter' && events[index][1].type === 'attentionSequence' && events[index][1]._close) {57 open = index;58 59 // Now walk back to find an opener.60 while (open--) {61 // Find a token that can open the closer.62 if (events[open][0] === 'exit' && events[open][1].type === 'attentionSequence' && events[open][1]._open &&63 // If the markers are the same:64 context.sliceSerialize(events[open][1]).charCodeAt(0) === context.sliceSerialize(events[index][1]).charCodeAt(0)) {65 // If the opening can close or the closing can open,66 // and the close size *is not* a multiple of three,67 // but the sum of the opening and closing size *is* multiple of three,68 // then don’t match.69 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)) {70 continue;71 }72 73 // Number of markers to use from the sequence.74 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;75 const start = {76 ...events[open][1].end77 };78 const end = {79 ...events[index][1].start80 };81 movePoint(start, -use);82 movePoint(end, use);83 openingSequence = {84 type: use > 1 ? "strongSequence" : "emphasisSequence",85 start,86 end: {87 ...events[open][1].end88 }89 };90 closingSequence = {91 type: use > 1 ? "strongSequence" : "emphasisSequence",92 start: {93 ...events[index][1].start94 },95 end96 };97 text = {98 type: use > 1 ? "strongText" : "emphasisText",99 start: {100 ...events[open][1].end101 },102 end: {103 ...events[index][1].start104 }105 };106 group = {107 type: use > 1 ? "strong" : "emphasis",108 start: {109 ...openingSequence.start110 },111 end: {112 ...closingSequence.end113 }114 };115 events[open][1].end = {116 ...openingSequence.start117 };118 events[index][1].start = {119 ...closingSequence.end120 };121 nextEvents = [];122 123 // If there are more markers in the opening, add them before.124 if (events[open][1].end.offset - events[open][1].start.offset) {125 nextEvents = push(nextEvents, [['enter', events[open][1], context], ['exit', events[open][1], context]]);126 }127 128 // Opening.129 nextEvents = push(nextEvents, [['enter', group, context], ['enter', openingSequence, context], ['exit', openingSequence, context], ['enter', text, context]]);130 131 // Always populated by defaults.132 133 // Between.134 nextEvents = push(nextEvents, resolveAll(context.parser.constructs.insideSpan.null, events.slice(open + 1, index), context));135 136 // Closing.137 nextEvents = push(nextEvents, [['exit', text, context], ['enter', closingSequence, context], ['exit', closingSequence, context], ['exit', group, context]]);138 139 // If there are more markers in the closing, add them after.140 if (events[index][1].end.offset - events[index][1].start.offset) {141 offset = 2;142 nextEvents = push(nextEvents, [['enter', events[index][1], context], ['exit', events[index][1], context]]);143 } else {144 offset = 0;145 }146 splice(events, open - 1, index - open + 3, nextEvents);147 index = open + nextEvents.length - offset - 2;148 break;149 }150 }151 }152 }153 154 // Remove remaining sequences.155 index = -1;156 while (++index < events.length) {157 if (events[index][1].type === 'attentionSequence') {158 events[index][1].type = 'data';159 }160 }161 return events;162}163 164/**165 * @this {TokenizeContext}166 * Context.167 * @type {Tokenizer}168 */169function tokenizeAttention(effects, ok) {170 const attentionMarkers = this.parser.constructs.attentionMarkers.null;171 const previous = this.previous;172 const before = classifyCharacter(previous);173 174 /** @type {NonNullable<Code>} */175 let marker;176 return start;177 178 /**179 * Before a sequence.180 *181 * ```markdown182 * > | **183 * ^184 * ```185 *186 * @type {State}187 */188 function start(code) {189 marker = code;190 effects.enter('attentionSequence');191 return inside(code);192 }193 194 /**195 * In a sequence.196 *197 * ```markdown198 * > | **199 * ^^200 * ```201 *202 * @type {State}203 */204 function inside(code) {205 if (code === marker) {206 effects.consume(code);207 return inside;208 }209 const token = effects.exit('attentionSequence');210 211 // To do: next major: move this to resolver, just like `markdown-rs`.212 const after = classifyCharacter(code);213 214 // Always populated by defaults.215 216 const open = !after || after === 2 && before || attentionMarkers.includes(code);217 const close = !before || before === 2 && after || attentionMarkers.includes(previous);218 token._open = Boolean(marker === 42 ? open : open && (before || !close));219 token._close = Boolean(marker === 42 ? close : close && (after || !open));220 return ok(code);221 }222}223 224/**225 * Move a point a bit.226 *227 * Note: `move` only works inside lines! It’s not possible to move past other228 * chunks (replacement characters, tabs, or line endings).229 *230 * @param {Point} point231 * Point.232 * @param {number} offset233 * Amount to move.234 * @returns {undefined}235 * Nothing.236 */237function movePoint(point, offset) {238 point.column += offset;239 point.offset += offset;240 point._bufferIndex += offset;241}