UNPKG

hitext

Version:

Text decoration done right

449 lines (357 loc) 13.7 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? module.exports = factory() : typeof define === 'function' && define.amd ? define(factory) : (global = global || self, global.hitext = factory()); }(this, (function () { 'use strict'; function newLineLength(source, i) { switch (source.charCodeAt(i)) { default: return 0; case 0x0a: // \n return 1; case 0x0d: // \r return i + 1 < source.length && source.charCodeAt(i + 1) === 0x0a ? 2 // \r\n : 1; // \r } } var utils = { newLineLength }; const { newLineLength: newLineLength$1 } = utils; var line = (source, createRange) => { let line = 1; let lineStart = 0; for (let i = 0; i < source.length; i++) { const nl = newLineLength$1(source, i); if (nl !== 0) { createRange(lineStart, i + nl, line++); lineStart = i + nl; i += nl - 1; } } createRange(lineStart, source.length, line++); }; const { newLineLength: newLineLength$2 } = utils; var lineContent = (source, createRange) => { let line = 1; let lineStart = 0; for (let i = 0; i < source.length; i++) { const nl = newLineLength$2(source, i); if (nl !== 0) { createRange(lineStart, i, line++); lineStart = i + nl; i += nl - 1; } } createRange(lineStart, source.length, line++); }; var match = function(pattern) { if (pattern instanceof RegExp) { const flags = pattern.flags.indexOf('g') !== -1 ? pattern.flags : pattern.flags + 'g'; const matchRx = new RegExp(pattern, flags); return function(source, createRange) { let match; while (match = matchRx.exec(source)) { createRange(match.index, match.index + match[0].length); } }; } pattern = String(pattern); return function(source, createRange) { let index = -1; while (true) { index = source.indexOf(pattern, index + 1); if (index === -1) { break; } createRange(index, index + pattern.length); } }; }; const { newLineLength: newLineLength$3 } = utils; var newLine = (source, createRange) => { let line = 1; for (let i = 0; i < source.length; i++) { const nl = newLineLength$3(source, i); if (nl !== 0) { createRange(i, i + nl, line++); i += nl - 1; } } }; var generator = { lines: line, lineContents: lineContent, matches: match, newlines: newLine }; var commonjsGlobal = typeof globalThis !== 'undefined' ? globalThis : typeof window !== 'undefined' ? window : typeof global !== 'undefined' ? global : typeof self !== 'undefined' ? self : {}; function createPrinter(base) { return forkPrinter.call(null, base); } function forkPrinter(extension) { const base = this === commonjsGlobal ? {} : this || {}; const newPrinter = {}; Object.assign(newPrinter, base, extension, { fork: forkPrinter.bind(newPrinter), ranges: Object.assign({}, base.ranges, extension && extension.ranges) }); if (typeof newPrinter.createHook !== 'function') { newPrinter.createHook = fn => fn(); } return newPrinter; } function forkPrinterSet(extension) { const newPrinterSet = Object.assign({}, this); for (let key in extension) { const typePrinter = extension[key]; if (!typePrinter || typeof typePrinter !== 'object') { continue; } if (hasOwnProperty.call(newPrinterSet, key)) { const existing = newPrinterSet[key]; newPrinterSet[key] = existing && typeof existing.fork === 'function' ? existing.fork(extension[key]) : existing; } else { newPrinterSet[key] = createPrinter(extension[key]); } } newPrinterSet.fork = forkPrinterSet.bind(newPrinterSet); return newPrinterSet; } var utils$1 = { createPrinter, forkPrinter, forkPrinterSet }; var noop = utils$1.createPrinter(); const { createPrinter: createPrinter$1 } = utils$1; var html = createPrinter$1({ print: chunk => chunk .replace(/&/g, '&amp;') .replace(/</g, '&lt;') .replace(/>/g, '&gt;') }); const { forkPrinterSet: forkPrinterSet$1 } = utils$1; var printer = forkPrinterSet$1.call({}, { noop: noop, html: html, tty: noop }); var generateRanges = function generateRanges(source, generators) { const ranges = []; generators.forEach(({ generate, marker }) => generate( source, (start, end, data) => ranges.push({ type: marker, start, end, data }) ) ); return ranges; }; const emptyString = () => ''; const noop$1 = function() {}; function ensureFunction(value, alt) { return typeof value === 'function' ? value : alt || noop$1; } var print = function print(source, ranges, printer) { const print = ensureFunction(printer.print, chunk => chunk); const printContext = Object.assign( Object.defineProperties(Object.create(null), { offset: { get: () => printedOffset }, line: { get: () => line }, column: { get: () => column }, start: { get: () => currentRange.start }, end: { get: () => currentRange.end }, data: { get: () => currentRange.data } }), ensureFunction(printer.createContext)() ); const openedRanges = []; let currentRange = { start: 0, end: source.length }; let rangeHooks = printer.ranges || {}; let rangePriority = []; let closingOffset = Infinity; let printedOffset = 0; let line = 1; let column = 1; let buffer = ''; buffer += ensureFunction(printer.open, emptyString)(printContext); // preprocess range hooks rangeHooks = [].concat( Object.getOwnPropertyNames(rangeHooks), Object.getOwnPropertySymbols(rangeHooks) ).reduce((result, type) => { let rangeHook = rangeHooks[type]; if (typeof rangeHook === 'function') { rangeHooks[type] = rangeHook = printer.createHook(rangeHook); } if (rangeHook) { rangePriority.push(type); result[type] = { open: ensureFunction(rangeHook.open, emptyString), close: ensureFunction(rangeHook.close, emptyString), print: ensureFunction(rangeHook.print, print) }; } return result; }, {}); // sort ranges ranges = ranges.slice().sort( (a, b) => a.start - b.start || b.end - a.end || rangePriority.indexOf(a.type) - rangePriority.indexOf(b.type) ); // main part const open = index => rangeHooks[(currentRange = openedRanges[index]).type].open(printContext) || ''; const close = index => rangeHooks[(currentRange = openedRanges[index]).type].close(printContext) || ''; const printChunk = (offset) => { if (printedOffset !== offset) { const substring = source.substring(printedOffset, offset); const printSubstr = openedRanges.length ? rangeHooks[openedRanges[openedRanges.length - 1].type].print : print; for (let i = printedOffset; i < offset; i++) { const ch = source.charCodeAt(i); if (ch === 0x0a /* \n */ || (ch === 0x0d /* \r */ && (i >= source.length || source.charCodeAt(i + 1) !== 0x0a))) { line++; column = 1; } else { column++; } } buffer += printSubstr(substring, printContext); printedOffset = offset; } }; const closeRanges = (offset) => { while (closingOffset <= offset) { printChunk(closingOffset); for (let j = openedRanges.length - 1; j >= 0; j--) { if (openedRanges[j].end !== closingOffset) { break; } buffer += close(j); openedRanges.pop(); } closingOffset = Infinity; for (let j = 0; j < openedRanges.length; j++) { if (openedRanges[j].end < closingOffset) { closingOffset = openedRanges[j].end; } } } }; for (let i = 0; i < ranges.length; i++) { const range = ranges[i]; let j = 0; // ignore ranges without a type hook if (rangeHooks.hasOwnProperty(range.type) === false) { continue; } // ignore ranges with wrong start/end values if (range.start > range.end || !Number.isFinite(range.start) || !Number.isFinite(range.end)) { continue; } closeRanges(range.start); printChunk(range.start); for (j = 0; j < openedRanges.length; j++) { if (openedRanges[j].end < range.end) { for (let k = openedRanges.length - 1; k >= j; k--) { buffer += close(k); } break; } } openedRanges.splice(j, 0, range); for (; j < openedRanges.length; j++) { buffer += open(j); } if (range.end < closingOffset) { closingOffset = range.end; } } closeRanges(source.length); printChunk(source.length); // print ranges out of source boundaries for (let i = openedRanges.length - 1; i >= 0; i--) { buffer += close(i); } // finish printing buffer += ensureFunction(printer.close, emptyString)(printContext) || ''; return buffer; }; function preprocessGenerator(marker, generate) { return { marker, generate }; } function preprocessPrinter(marker, printer) { const newPrinter = {}; for (let key in printer) { newPrinter[key] = { ranges: { [marker]: printer[key] } }; } return newPrinter; } function pipelineChain(generators, printerSet, defaultPrinterType) { const pipeline = (source, printerType) => { const printer$1 = printerSet[printerType || defaultPrinterType] || printer.noop; const ranges = generateRanges(source, generators); const result = print(source, ranges, printer$1); return result; }; return Object.assign(pipeline, { print: pipeline, generateRanges(source) { return generateRanges(source, generators); }, use(plugin, printer) { const marker = Symbol(plugin.name); const ranges = plugin.ranges || plugin; const generate = Array.isArray(ranges) ? (source, createRange) => ranges.forEach(range => createRange(...range)) : ranges; if (typeof generate !== 'function') { return pipeline; // exception? } if (!printer) { printer = plugin.printer; } if (!printer) { return pipeline; // exception? } return pipelineChain( generators.concat(preprocessGenerator(marker, generate)), printerSet.fork(preprocessPrinter(marker, printer)), defaultPrinterType ); }, printer(selectedPrinter) { return pipelineChain(generators, printerSet, selectedPrinter); } }); } function hitext(plugins, printerType, printerSet) { let pipeline = pipelineChain([], printerSet || printer, printerType); if (Array.isArray(plugins)) { pipeline = plugins.reduce( (pipeline, plugin) => Array.isArray(plugin) ? pipeline.use(...plugin) : pipeline.use(plugin), pipeline ); } return pipeline; } var src = Object.assign(hitext, { gen: generator, printer: Object.assign((...args) => hitext().printer(...args), printer), use(...args) { return hitext().use(...args); } }); return src; })));