Skip to content

Commit

Permalink
util: add colorize functionality
Browse files Browse the repository at this point in the history
The colorize object allows to format strings with ansi color codes
so that the string is stylized on a terminal.

This is a utility function that should not yet be exposed to the
users to allow some further polishing first.

Signed-off-by: Ruben Bridgewater <ruben@bridgewater.de>
  • Loading branch information
BridgeAR committed Jun 21, 2022
1 parent 93728c6 commit 77f2290
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 5 deletions.
42 changes: 41 additions & 1 deletion doc/api/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,47 @@ The `--throw-deprecation` command-line flag and `process.throwDeprecation`
property take precedence over `--trace-deprecation` and
`process.traceDeprecation`.

## `util.format(format[, ...args])`
## `util.colorize`
<!-- YAML
added: REPLACEME
-->

The `util.colorize` object provides functions that add ansi color codes to the
provided string. These may be used to style terminal output.

### `util.colorize.<style>[. ...<style>](string[, ...string])`
<!-- YAML
added: REPLACEME
-->

* `string` {string} The string that is formatted by the chosen style.
* Returns: {string} The formatted string

The API allows to be used with a builder/chaining pattern to add multiple styles
in one call. Nesting color codes is supported.

```js
const { colorize } = util;

console.log(
`${colorize.green('Heads up')}: only the "Heads up" is green`
);

console.log(
colorize.green('green', colorize.yellow('yellow'), 'green')
)

console.log(
colorize.bold.underline.red('bold red underline')
)

const info = colorize.italics.blue.bgYellow;
console.log(
info('italic blue with yellow background')
)
```

## `util.format(format[, args...])`

<!-- YAML
added: v0.5.3
Expand Down
7 changes: 5 additions & 2 deletions lib/internal/debugger/inspect_repl.js
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,10 @@ const vm = require('vm');
const { fileURLToPath } = require('internal/url');

const { customInspectSymbol } = require('internal/util');
const { inspect: utilInspect } = require('internal/util/inspect');
const {
inspect: utilInspect,
colorize
} = require('internal/util/inspect');
const debuglog = require('internal/util/debuglog').debuglog('inspect');

const SHORTCUTS = {
Expand Down Expand Up @@ -162,7 +165,7 @@ function markSourceColumn(sourceText, position, useColors) {
// Colourize char if stdout supports colours
if (useColors) {
tail = RegExpPrototypeSymbolReplace(/(.+?)([^\w]|$)/, tail,
'\u001b[32m$1\u001b[39m$2');
`${colorize.gray('$1')}$2`);
}

// Return source line with coloured char at `position`
Expand Down
4 changes: 2 additions & 2 deletions lib/internal/repl/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
completionPreview = suffix;

const result = repl.useColors ?
`\u001b[90m${suffix}\u001b[39m` :
colorize.gray(suffix) :
` // ${suffix}`;

const { cursorPos, displayPos } = getPreviewPos();
Expand Down Expand Up @@ -443,7 +443,7 @@ function setupPreview(repl, contextSymbol, bufferSymbol, active) {
}

const result = repl.useColors ?
`\u001b[90m${inspected}\u001b[39m` :
colorize.gray(inspected) :
`// ${inspected}`;

const { cursorPos, displayPos } = getPreviewPos();
Expand Down
77 changes: 77 additions & 0 deletions lib/internal/util/inspect.js
Original file line number Diff line number Diff line change
Expand Up @@ -439,6 +439,82 @@ defineColorAlias('inverse', 'swapColors');
defineColorAlias('inverse', 'swapcolors');
defineColorAlias('doubleunderline', 'doubleUnderline');

// TODO(BridgeAR): Consider using a class instead and require the user to instantiate it
// before using to declare their color support (or use a default, if none
// provided).
const colorize = {};

function defineColorGetter({ style, codes, enumerable }) {
function color(...strings) {
let result = '';
const ansiCodes = this.ansiCodes
let string = strings[0];
for (let i = 1; i < strings.length; i++) {
string += ' ' + strings[i];
}

// Fast path
const searchCode = ansiCodes.length === 1 ? ansiCodes[0].end : '\u001b[';
const ansiCodeStart = string.indexOf(searchCode);
if (ansiCodeStart === -1) {
for (const code of ansiCodes) {
result += code.start;
}
} else {
// Slow path
const start = string.slice(0, ansiCodeStart - 1);
let middle = string.slice(ansiCodeStart - 1, -4);
const end = string.slice(-4);

for (const code of ansiCodes) {
result += code.start;
// Continue former colors by finding end points and continuing from there.
middle = middle.replaceAll(code.end, `$&${code.start}`)
}
string = `${start}${middle}${end}`;
}

result += string;
for (let i = ansiCodes.length - 1; i >= 0; i--) {
result += ansiCodes[i].end;
}

return result;
}

Object.defineProperty(colorize, style, {
__proto__: null,
get: function () {
if (typeof this === 'function') {
this.ansiCodes.push(codes);
return this;
}
const ansiCodes = [codes];
const context = {
ansiCodes
};
let boundColor = color.bind(context);
// Enable chaining.
Object.setPrototypeOf(boundColor, colorize);
boundColor.ansiCodes = ansiCodes;
return boundColor;
},
enumerable,
});
}

for (const [style, descriptor] of Object.entries(Object.getOwnPropertyDescriptors(inspect.colors))) {
const value = descriptor.value ?? descriptor.get.call(inspect.colors);
defineColorGetter({
style,
codes: {
start: `\u001b[${value[0]}m`,
end: `\u001b[${value[1]}m`
},
enumerable: descriptor.enumerable
});
}

// TODO(BridgeAR): Add function style support for more complex styles.
// Don't use 'blue' not visible on cmd.exe
inspect.styles = ObjectAssign(ObjectCreate(null), {
Expand Down Expand Up @@ -2290,6 +2366,7 @@ function stripVTControlCharacters(str) {
}

module.exports = {
colorize,
inspect,
format,
formatWithOptions,
Expand Down

0 comments on commit 77f2290

Please sign in to comment.