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
|
## Legacy mode
|
||||||
|
|
||||||
> Stability: 0 - Deprecated: Use strict mode instead.
|
> 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
|
A `number` specifying the number of rows the TTY currently has. This property
|
||||||
is updated whenever the `'resize'` event is emitted.
|
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)
|
## tty.isatty(fd)
|
||||||
<!-- YAML
|
<!-- YAML
|
||||||
added: v0.5.8
|
added: v0.5.8
|
||||||
|
@ -14,6 +14,10 @@ const kCode = Symbol('code');
|
|||||||
const kInfo = Symbol('info');
|
const kInfo = Symbol('info');
|
||||||
const messages = new Map();
|
const messages = new Map();
|
||||||
|
|
||||||
|
var green = '';
|
||||||
|
var red = '';
|
||||||
|
var white = '';
|
||||||
|
|
||||||
const { errmap } = process.binding('uv');
|
const { errmap } = process.binding('uv');
|
||||||
const { kMaxLength } = process.binding('buffer');
|
const { kMaxLength } = process.binding('buffer');
|
||||||
const { defineProperty } = Object;
|
const { defineProperty } = Object;
|
||||||
@ -143,7 +147,7 @@ function createErrDiff(actual, expected, operator) {
|
|||||||
const expectedLines = util
|
const expectedLines = util
|
||||||
.inspect(expected, { compact: false }).split('\n');
|
.inspect(expected, { compact: false }).split('\n');
|
||||||
const msg = `Input A expected to ${operator} input B:\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';
|
const skippedMsg = ' ... Lines skipped';
|
||||||
|
|
||||||
// Remove all ending lines that match (this optimizes the output for
|
// Remove all ending lines that match (this optimizes the output for
|
||||||
@ -189,7 +193,7 @@ function createErrDiff(actual, expected, operator) {
|
|||||||
printedLines++;
|
printedLines++;
|
||||||
}
|
}
|
||||||
lastPos = i;
|
lastPos = i;
|
||||||
other += `\n\u001b[32m+\u001b[39m ${expectedLines[i]}`;
|
other += `\n${green}+${white} ${expectedLines[i]}`;
|
||||||
printedLines++;
|
printedLines++;
|
||||||
// Only extra actual lines exist
|
// Only extra actual lines exist
|
||||||
} else if (expectedLines.length < i + 1) {
|
} else if (expectedLines.length < i + 1) {
|
||||||
@ -205,7 +209,7 @@ function createErrDiff(actual, expected, operator) {
|
|||||||
printedLines++;
|
printedLines++;
|
||||||
}
|
}
|
||||||
lastPos = i;
|
lastPos = i;
|
||||||
res += `\n\u001b[31m-\u001b[39m ${actualLines[i]}`;
|
res += `\n${red}-${white} ${actualLines[i]}`;
|
||||||
printedLines++;
|
printedLines++;
|
||||||
// Lines diverge
|
// Lines diverge
|
||||||
} else if (actualLines[i] !== expectedLines[i]) {
|
} else if (actualLines[i] !== expectedLines[i]) {
|
||||||
@ -221,8 +225,8 @@ function createErrDiff(actual, expected, operator) {
|
|||||||
printedLines++;
|
printedLines++;
|
||||||
}
|
}
|
||||||
lastPos = i;
|
lastPos = i;
|
||||||
res += `\n\u001b[31m-\u001b[39m ${actualLines[i]}`;
|
res += `\n${red}-${white} ${actualLines[i]}`;
|
||||||
other += `\n\u001b[32m+\u001b[39m ${expectedLines[i]}`;
|
other += `\n${green}+${white} ${expectedLines[i]}`;
|
||||||
printedLines += 2;
|
printedLines += 2;
|
||||||
// Lines are identical
|
// Lines are identical
|
||||||
} else {
|
} else {
|
||||||
@ -258,7 +262,14 @@ class AssertionError extends Error {
|
|||||||
if (message != null) {
|
if (message != null) {
|
||||||
super(message);
|
super(message);
|
||||||
} else {
|
} 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)
|
if (actual && actual.stack && actual instanceof Error)
|
||||||
actual = `${actual.name}: ${actual.message}`;
|
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 errnoException = util._errnoException;
|
||||||
const errors = require('internal/errors');
|
const errors = require('internal/errors');
|
||||||
const readline = require('readline');
|
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) {
|
function isatty(fd) {
|
||||||
return Number.isInteger(fd) && fd >= 0 && isTTY(fd);
|
return Number.isInteger(fd) && fd >= 0 && isTTY(fd);
|
||||||
@ -104,6 +112,70 @@ inherits(WriteStream, net.Socket);
|
|||||||
|
|
||||||
WriteStream.prototype.isTTY = true;
|
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() {
|
WriteStream.prototype._refreshSize = function() {
|
||||||
var oldCols = this.columns;
|
var oldCols = this.columns;
|
||||||
|
@ -788,10 +788,13 @@ common.expectsError(
|
|||||||
Error.stackTraceLimit = tmpLimit;
|
Error.stackTraceLimit = tmpLimit;
|
||||||
|
|
||||||
// Test error diffs
|
// Test error diffs
|
||||||
|
const colors = process.stdout.isTTY && process.stdout.getColorDepth() > 1;
|
||||||
const start = 'Input A expected to deepStrictEqual input B:';
|
const start = 'Input A expected to deepStrictEqual input B:';
|
||||||
const actExp = '\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m';
|
const actExp = colors ?
|
||||||
const plus = '\u001b[32m+\u001b[39m';
|
'\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m' :
|
||||||
const minus = '\u001b[31m-\u001b[39m';
|
'+ expected - actual';
|
||||||
|
const plus = colors ? '\u001b[32m+\u001b[39m' : '+';
|
||||||
|
const minus = colors ? '\u001b[31m-\u001b[39m' : '-';
|
||||||
let message = [
|
let message = [
|
||||||
start,
|
start,
|
||||||
`${actExp} ... Lines skipped`,
|
`${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