hitext

Text decoration done right

Usage no npm install needed!

<script type="module">
  import hitext from 'https://cdn.skypack.dev/hitext';
</script>

README

HiText logo

HiText

NPM version Build Status Coverage Status

HiText is a basis for a text (source code) decoration. The main goal is to provide a universal way to combine libraries decorating text and an output in the required format (HTML, TTY etc). The only prerequsites you need are a source text and set of ranges with some options, the rest will be done by HiText.

Why?

Just imagine you want to output in HTML a JavaScript code with syntax highlighting and to spotlight some fragments of it. You can use a library for syntax highlighting, that's not a problem. For fragment spotlighting you can get substrings and wrap them into HTML tags. Both operations are not so hard, but the problem is to combine results of them. Each operation adds HTML tags to a result, so another operation will not perform as expected because of HTML tags.

The solution is to change such operations to produce a set of ranges instead of markup. Once ranges is generated, HiText can merge them by universal rules and get a stream of segments. As a final step, segment stream is used to generate open and close tags when needed.

The approach allows to combine any number of decorators (which became range generators). Moreover, using a source and a range set, it's possible to output a result in any format beside HTML. An output format is defined by a printer.

Features

  • Universal way to combine a number of decorators (generators)
  • Much easier to make a generator
  • Parsers with formatting losses can be used. No AST mutations required to decorate an input
  • Flexible setup for an output. Build-in printers: HTML, TTY (terminal)

Example

const hitext = require('hitext');
const prism = require('hitext-prism');
const spotlightRanges = [[6, 11], [19, 24]];
const spotlightPrinter = {
    html: {
        open() { return '<span class="spotlight">'; },
        close() { return '</span>'; }
    }
}
const lineNumber = {
    ranges: hitext.gen.lines,
    printer: {
        html: {
            open({ line }) { return line + ' | ' }
        }
    }
};

// 1. Create pipeline
const highlightJsAndSpotlight = hitext([
    prism('js'),
    [spotlightRanges, spotlightPrinter],
    lineNumber
], 'html');
// or
const highlightJsAndSpotlight = hitext([prism('js')], 'html')
    .use(spotlightRanges, spotlightPrinter)
    .use(lineNumber);
// or
const highlightJsAndSpotlight = hitext
    .use(prism('js'))
    .use({
        ranges: spotlightRanges,
        printer: spotlightPrinter
    })
    .use(lineNumber)
    .printer('html');

// 2. Apply to a text
console.log(highlightJsAndSpotlight('const a = 1;\nconst b = 2;'));

Output:

1 | <span class="token keyword">const</span> <span class="spotlight">a <span class="token operator">=</span> <span class="token number">1</span></span><span class="token punctuation">;</span>
2 | <span class="token keyword">const</span> <span class="spotlight">b <span class="token operator">=</span> <span class="token number">2</span></span><span class="token punctuation">;</span>

Build-in generators

lines

const hitext = require('hitext');

console.log(
    hitext()
        .use(hitext.gen.lines, {
            html: {
                open: ({ line }) => `<span title="line #${line}">`,
                close: () => '</span>'
            }
        })
        .print('foo\nbar', 'html')
);
// '<span title="line #1">foo\n</span><span title="line #2">foo</span>'

lineContents

const hitext = require('hitext');

console.log(
    hitext()
        .use(hitext.gen.lineContents, {
            html: {
                open: ({ line }) => `<span title="line #${line}">`,
                close: () => '</span>'
            }
        })
        .print('foo\nbar', 'html')
);
// '<span title="line #1">foo</span>\n<span title="line #2">foo</span>'

newlines

const hitext = require('hitext');

console.log(
    hitext()
        .use(hitext.gen.newlines, {
            html: {
                open: ({ line }) => `<span title="line #${line}">`,
                close: () => '</span>'
            }
        })
        .print('foo\nbar', 'html')
);
// 'foo<span title="line #1">\n</span>foo'

matches(pattern)

const hitext = require('hitext');
const matchPrinter = {
    html: {
        open: () => `<span class="match">`,
        close: () => '</span>'
    }
};

console.log(
    hitext()
        .use(hitext.gen.matches('world'), matchPrinter)
        .print('Hello world! Hello world!', 'html')
);
// Hello <span class="match">world</span>! Hello <span class="match">world</span>!

console.log(
    hitext()
        .use(hitext.gen.matches(/\w+/), matchPrinter)
        .print('Hello world!', 'html')
);
// <span class="match">Hello</span> <span class="match">world</span>!

Build-in printers

html

hitext
    .use(whatever, {
        html: {
            open: () => '<span class="example">',
            close: () => '</span>'
        }
    })
    .printer('html');

tty

hitext
    .use(whatever, {
        tty({ createStyle }) => createStyle('bgWhite', 'red')
    })
    .use(whatever, {
        tty({ createStyleMap }) => createStyleMap({
            foo: 'green',
            bar: ['bgWhite', 'red']
        })
    })
    .use(whatever, {
        tty({ createStyleMap }) => createStyleMap({
            foo: 'green',
            bar: ['bgWhite', 'red']
        }, ({ data }) => {
            // specify the way how to transform data to map key
            // when this argument is not specified `({ data }) => data` is using
            return data.magickField;
        })
    })
    .printer('tty');

License

MIT