tty: add getColorDepth function
Right now it is very difficult to determine if a terminal supports colors or not. This function adds this functionality by detecting environment variables and checking process. PR-URL: https://github.com/nodejs/node/pull/17615 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Matteo Collina <matteo.collina@gmail.com>
This commit is contained in:
parent
19bff313be
commit
bb9cedb0f0
@ -65,6 +65,9 @@ AssertionError [ERR_ASSERTION]: Input A expected to deepStrictEqual input B:
|
||||
]
|
||||
```
|
||||
|
||||
To deactivate the colors, use the `NODE_DISABLE_COLORS` environment variable.
|
||||
Please note that this will also deactivate the colors in the REPL.
|
||||
|
||||
## Legacy mode
|
||||
|
||||
> Stability: 0 - Deprecated: Use strict mode instead.
|
||||
|
@ -121,6 +121,32 @@ added: v0.7.7
|
||||
A `number` specifying the number of rows the TTY currently has. This property
|
||||
is updated whenever the `'resize'` event is emitted.
|
||||
|
||||
### writeStream.getColorDepth([env])
|
||||
<!-- YAML
|
||||
added: REPLACEME
|
||||
-->
|
||||
|
||||
* `env` {object} A object containing the environment variables to check.
|
||||
Defaults to `process.env`.
|
||||
* Returns: {number}
|
||||
|
||||
Returns:
|
||||
* 1 for 2,
|
||||
* 4 for 16,
|
||||
* 8 for 256,
|
||||
* 24 for 16,777,216
|
||||
colors supported.
|
||||
|
||||
Use this to determine what colors the terminal supports. Due to the nature of
|
||||
colors in terminals it is possible to either have false positives or false
|
||||
negatives. It depends on process information and the environment variables that
|
||||
may lie about what terminal is used.
|
||||
To enforce a specific behavior without relying on `process.env` it is possible
|
||||
to pass in an object with different settings.
|
||||
|
||||
Use the `NODE_DISABLE_COLORS` environment variable to enforce this function to
|
||||
always return 1.
|
||||
|
||||
## tty.isatty(fd)
|
||||
<!-- YAML
|
||||
added: v0.5.8
|
||||
|
@ -14,6 +14,10 @@ const kCode = Symbol('code');
|
||||
const kInfo = Symbol('info');
|
||||
const messages = new Map();
|
||||
|
||||
var green = '';
|
||||
var red = '';
|
||||
var white = '';
|
||||
|
||||
const { errmap } = process.binding('uv');
|
||||
const { kMaxLength } = process.binding('buffer');
|
||||
const { defineProperty } = Object;
|
||||
@ -143,7 +147,7 @@ function createErrDiff(actual, expected, operator) {
|
||||
const expectedLines = util
|
||||
.inspect(expected, { compact: false }).split('\n');
|
||||
const msg = `Input A expected to ${operator} input B:\n` +
|
||||
'\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m';
|
||||
`${green}+ expected${white} ${red}- actual${white}`;
|
||||
const skippedMsg = ' ... Lines skipped';
|
||||
|
||||
// Remove all ending lines that match (this optimizes the output for
|
||||
@ -189,7 +193,7 @@ function createErrDiff(actual, expected, operator) {
|
||||
printedLines++;
|
||||
}
|
||||
lastPos = i;
|
||||
other += `\n\u001b[32m+\u001b[39m ${expectedLines[i]}`;
|
||||
other += `\n${green}+${white} ${expectedLines[i]}`;
|
||||
printedLines++;
|
||||
// Only extra actual lines exist
|
||||
} else if (expectedLines.length < i + 1) {
|
||||
@ -205,7 +209,7 @@ function createErrDiff(actual, expected, operator) {
|
||||
printedLines++;
|
||||
}
|
||||
lastPos = i;
|
||||
res += `\n\u001b[31m-\u001b[39m ${actualLines[i]}`;
|
||||
res += `\n${red}-${white} ${actualLines[i]}`;
|
||||
printedLines++;
|
||||
// Lines diverge
|
||||
} else if (actualLines[i] !== expectedLines[i]) {
|
||||
@ -221,8 +225,8 @@ function createErrDiff(actual, expected, operator) {
|
||||
printedLines++;
|
||||
}
|
||||
lastPos = i;
|
||||
res += `\n\u001b[31m-\u001b[39m ${actualLines[i]}`;
|
||||
other += `\n\u001b[32m+\u001b[39m ${expectedLines[i]}`;
|
||||
res += `\n${red}-${white} ${actualLines[i]}`;
|
||||
other += `\n${green}+${white} ${expectedLines[i]}`;
|
||||
printedLines += 2;
|
||||
// Lines are identical
|
||||
} else {
|
||||
@ -258,7 +262,14 @@ class AssertionError extends Error {
|
||||
if (message != null) {
|
||||
super(message);
|
||||
} else {
|
||||
if (util === null) util = require('util');
|
||||
if (util === null) {
|
||||
util = require('util');
|
||||
if (process.stdout.isTTY && process.stdout.getColorDepth() !== 1) {
|
||||
green = '\u001b[32m';
|
||||
white = '\u001b[39m';
|
||||
red = '\u001b[31m';
|
||||
}
|
||||
}
|
||||
|
||||
if (actual && actual.stack && actual instanceof Error)
|
||||
actual = `${actual.name}: ${actual.message}`;
|
||||
|
72
lib/tty.js
72
lib/tty.js
@ -28,6 +28,14 @@ const { inherits } = util;
|
||||
const errnoException = util._errnoException;
|
||||
const errors = require('internal/errors');
|
||||
const readline = require('readline');
|
||||
const { release } = require('os');
|
||||
|
||||
const OSRelease = release().split('.');
|
||||
|
||||
const COLORS_2 = 1;
|
||||
const COLORS_16 = 4;
|
||||
const COLORS_256 = 8;
|
||||
const COLORS_16m = 24;
|
||||
|
||||
function isatty(fd) {
|
||||
return Number.isInteger(fd) && fd >= 0 && isTTY(fd);
|
||||
@ -104,6 +112,70 @@ inherits(WriteStream, net.Socket);
|
||||
|
||||
WriteStream.prototype.isTTY = true;
|
||||
|
||||
WriteStream.prototype.getColorDepth = function(env = process.env) {
|
||||
if (env.NODE_DISABLE_COLORS || env.TERM === 'dumb' && !env.COLORTERM) {
|
||||
return COLORS_2;
|
||||
}
|
||||
|
||||
if (process.platform === 'win32') {
|
||||
// Windows 10 build 10586 is the first Windows release that supports 256
|
||||
// colors. Windows 10 build 14931 is the first release that supports
|
||||
// 16m/TrueColor.
|
||||
if (+OSRelease[0] >= 10) {
|
||||
const build = +OSRelease[2];
|
||||
if (build >= 14931)
|
||||
return COLORS_16m;
|
||||
if (build >= 10586)
|
||||
return COLORS_256;
|
||||
}
|
||||
|
||||
return COLORS_16;
|
||||
}
|
||||
|
||||
if (env.TMUX) {
|
||||
return COLORS_256;
|
||||
}
|
||||
|
||||
if (env.CI) {
|
||||
if ('TRAVIS' in env || 'CIRCLECI' in env || 'APPVEYOR' in env ||
|
||||
'GITLAB_CI' in env || env.CI_NAME === 'codeship') {
|
||||
return COLORS_256;
|
||||
}
|
||||
return COLORS_2;
|
||||
}
|
||||
|
||||
if ('TEAMCITY_VERSION' in env) {
|
||||
return /^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ?
|
||||
COLORS_16 : COLORS_2;
|
||||
}
|
||||
|
||||
switch (env.TERM_PROGRAM) {
|
||||
case 'iTerm.app':
|
||||
if (!env.TERM_PROGRAM_VERSION ||
|
||||
/^[0-2]\./.test(env.TERM_PROGRAM_VERSION)) {
|
||||
return COLORS_256;
|
||||
}
|
||||
return COLORS_16m;
|
||||
case 'HyperTerm':
|
||||
case 'Hyper':
|
||||
case 'MacTerm':
|
||||
return COLORS_16m;
|
||||
case 'Apple_Terminal':
|
||||
return COLORS_256;
|
||||
}
|
||||
|
||||
if (env.TERM) {
|
||||
if (/^xterm-256/.test(env.TERM))
|
||||
return COLORS_256;
|
||||
if (/^screen|^xterm|^vt100|color|ansi|cygwin|linux/i.test(env.TERM))
|
||||
return COLORS_16;
|
||||
}
|
||||
|
||||
if (env.COLORTERM)
|
||||
return COLORS_16;
|
||||
|
||||
return COLORS_2;
|
||||
};
|
||||
|
||||
WriteStream.prototype._refreshSize = function() {
|
||||
var oldCols = this.columns;
|
||||
|
@ -788,10 +788,13 @@ common.expectsError(
|
||||
Error.stackTraceLimit = tmpLimit;
|
||||
|
||||
// Test error diffs
|
||||
const colors = process.stdout.isTTY && process.stdout.getColorDepth() > 1;
|
||||
const start = 'Input A expected to deepStrictEqual input B:';
|
||||
const actExp = '\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m';
|
||||
const plus = '\u001b[32m+\u001b[39m';
|
||||
const minus = '\u001b[31m-\u001b[39m';
|
||||
const actExp = colors ?
|
||||
'\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m' :
|
||||
'+ expected - actual';
|
||||
const plus = colors ? '\u001b[32m+\u001b[39m' : '+';
|
||||
const minus = colors ? '\u001b[31m-\u001b[39m' : '-';
|
||||
let message = [
|
||||
start,
|
||||
`${actExp} ... Lines skipped`,
|
||||
|
52
test/parallel/test-tty-get-color-depth.js
Normal file
52
test/parallel/test-tty-get-color-depth.js
Normal file
@ -0,0 +1,52 @@
|
||||
'use strict';
|
||||
|
||||
const common = require('../common');
|
||||
const assert = require('assert').strict;
|
||||
/* eslint-disable no-restricted-properties */
|
||||
const { openSync } = require('fs');
|
||||
const tty = require('tty');
|
||||
|
||||
const { WriteStream } = require('tty');
|
||||
|
||||
// Do our best to grab a tty fd.
|
||||
function getTTYfd() {
|
||||
const ttyFd = [0, 1, 2, 4, 5].find(tty.isatty);
|
||||
if (ttyFd === undefined) {
|
||||
try {
|
||||
return openSync('/dev/tty');
|
||||
} catch (e) {
|
||||
// There aren't any tty fd's available to use.
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return ttyFd;
|
||||
}
|
||||
|
||||
const fd = getTTYfd();
|
||||
|
||||
// Give up if we did not find a tty
|
||||
if (fd === -1)
|
||||
common.skip();
|
||||
|
||||
const writeStream = new WriteStream(fd);
|
||||
|
||||
let depth = writeStream.getColorDepth();
|
||||
|
||||
assert.equal(typeof depth, 'number');
|
||||
assert(depth >= 1 && depth <= 24);
|
||||
|
||||
// If the terminal does not support colors, skip the rest
|
||||
if (depth === 1)
|
||||
common.skip();
|
||||
|
||||
assert.notEqual(writeStream.getColorDepth({ TERM: 'dumb' }), depth);
|
||||
|
||||
// Deactivate colors
|
||||
const tmp = process.env.NODE_DISABLE_COLORS;
|
||||
process.env.NODE_DISABLE_COLORS = 1;
|
||||
|
||||
depth = writeStream.getColorDepth();
|
||||
|
||||
assert.equal(depth, 1);
|
||||
|
||||
process.env.NODE_DISABLE_COLORS = tmp;
|
Loading…
x
Reference in New Issue
Block a user