hitext
Version:
Text decoration done right
449 lines (357 loc) • 13.7 kB
JavaScript
(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, '&')
.replace(/</g, '<')
.replace(/>/g, '>')
});
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;
})));