Skip to content

Commit ead727c

Browse files
BridgeARMylesBorins
authored andcommitted
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. Backport-PR-URL: #19230 PR-URL: #17615 Reviewed-By: James M Snell <[email protected]> Reviewed-By: Matteo Collina <[email protected]>
1 parent 5aa3a2d commit ead727c

File tree

6 files changed

+175
-8
lines changed

6 files changed

+175
-8
lines changed

‎doc/api/assert.md‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@ AssertionError [ERR_ASSERTION]: Input A expected to deepStrictEqual input B:
6565
]
6666
```
6767

68+
To deactivate the colors, use the `NODE_DISABLE_COLORS` environment variable.
69+
Please note that this will also deactivate the colors in the REPL.
70+
6871
## Legacy mode
6972

7073
> Stability: 0 - Deprecated: Use strict mode instead.

‎doc/api/tty.md‎

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,32 @@ added: v0.7.7
121121
A `number` specifying the number of rows the TTY currently has. This property
122122
is updated whenever the `'resize'` event is emitted.
123123

124+
### writeStream.getColorDepth([env])
125+
<!-- YAML
126+
added: REPLACEME
127+
-->
128+
129+
*`env`{object} A object containing the environment variables to check.
130+
Defaults to `process.env`.
131+
* Returns:{number}
132+
133+
Returns:
134+
* 1 for 2,
135+
* 4 for 16,
136+
* 8 for 256,
137+
* 24 for 16,777,216
138+
colors supported.
139+
140+
Use this to determine what colors the terminal supports. Due to the nature of
141+
colors in terminals it is possible to either have false positives or false
142+
negatives. It depends on process information and the environment variables that
143+
may lie about what terminal is used.
144+
To enforce a specific behavior without relying on `process.env` it is possible
145+
to pass in an object with different settings.
146+
147+
Use the `NODE_DISABLE_COLORS` environment variable to enforce this function to
148+
always return 1.
149+
124150
## tty.isatty(fd)
125151
<!-- YAML
126152
added: v0.5.8

‎lib/internal/errors.js‎

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,10 @@
1313
constkCode=Symbol('code');
1414
constmessages=newMap();
1515

16+
vargreen='';
17+
varred='';
18+
varwhite='';
19+
1620
const{
1721
UV_EAI_MEMORY,
1822
UV_EAI_NODATA,
@@ -90,7 +94,7 @@ function createErrDiff(actual, expected, operator){
9094
constexpectedLines=util
9195
.inspect(expected,{compact: false}).split('\n');
9296
constmsg=`Input A expected to ${operator} input B:\n`+
93-
'\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m';
97+
`${green}+ expected${white}${red}- actual${white}`;
9498
constskippedMsg=' ... Lines skipped';
9599

96100
// Remove all ending lines that match (this optimizes the output for
@@ -136,7 +140,7 @@ function createErrDiff(actual, expected, operator){
136140
printedLines++;
137141
}
138142
lastPos=i;
139-
other+=`\n\u001b[32m+\u001b[39m${expectedLines[i]}`;
143+
other+=`\n${green}+${white}${expectedLines[i]}`;
140144
printedLines++;
141145
// Only extra actual lines exist
142146
}elseif(expectedLines.length<i+1){
@@ -152,7 +156,7 @@ function createErrDiff(actual, expected, operator){
152156
printedLines++;
153157
}
154158
lastPos=i;
155-
res+=`\n\u001b[31m-\u001b[39m${actualLines[i]}`;
159+
res+=`\n${red}-${white}${actualLines[i]}`;
156160
printedLines++;
157161
// Lines diverge
158162
}elseif(actualLines[i]!==expectedLines[i]){
@@ -168,8 +172,8 @@ function createErrDiff(actual, expected, operator){
168172
printedLines++;
169173
}
170174
lastPos=i;
171-
res+=`\n\u001b[31m-\u001b[39m${actualLines[i]}`;
172-
other+=`\n\u001b[32m+\u001b[39m${expectedLines[i]}`;
175+
res+=`\n${red}-${white}${actualLines[i]}`;
176+
other+=`\n${green}+${white}${expectedLines[i]}`;
173177
printedLines+=2;
174178
// Lines are identical
175179
}else{
@@ -205,6 +209,13 @@ class AssertionError extends Error{
205209
if(message!=null){
206210
super(message);
207211
}else{
212+
if(util_===null&&
213+
process.stdout.isTTY&&
214+
process.stdout.getColorDepth()!==1){
215+
green='\u001b[32m';
216+
white='\u001b[39m';
217+
red='\u001b[31m';
218+
}
208219
constutil=lazyUtil();
209220
if(actual&&actual.stack&&actualinstanceofError)
210221
actual=`${actual.name}: ${actual.message}`;

‎lib/tty.js‎

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,14 @@ const net = require('net');
2626
const{TTY, isTTY }=process.binding('tty_wrap');
2727
consterrors=require('internal/errors');
2828
constreadline=require('readline');
29+
const{ release }=require('os');
30+
31+
constOSRelease=release().split('.');
32+
33+
constCOLORS_2=1;
34+
constCOLORS_16=4;
35+
constCOLORS_256=8;
36+
constCOLORS_16m=24;
2937

3038
functionisatty(fd){
3139
returnNumber.isInteger(fd)&&fd>=0&&isTTY(fd);
@@ -90,6 +98,70 @@ inherits(WriteStream, net.Socket);
9098

9199
WriteStream.prototype.isTTY=true;
92100

101+
WriteStream.prototype.getColorDepth=function(env=process.env){
102+
if(env.NODE_DISABLE_COLORS||env.TERM==='dumb'&&!env.COLORTERM){
103+
returnCOLORS_2;
104+
}
105+
106+
if(process.platform==='win32'){
107+
// Windows 10 build 10586 is the first Windows release that supports 256
108+
// colors. Windows 10 build 14931 is the first release that supports
109+
// 16m/TrueColor.
110+
if(+OSRelease[0]>=10){
111+
constbuild=+OSRelease[2];
112+
if(build>=14931)
113+
returnCOLORS_16m;
114+
if(build>=10586)
115+
returnCOLORS_256;
116+
}
117+
118+
returnCOLORS_16;
119+
}
120+
121+
if(env.TMUX){
122+
returnCOLORS_256;
123+
}
124+
125+
if(env.CI){
126+
if('TRAVIS'inenv||'CIRCLECI'inenv||'APPVEYOR'inenv||
127+
'GITLAB_CI'inenv||env.CI_NAME==='codeship'){
128+
returnCOLORS_256;
129+
}
130+
returnCOLORS_2;
131+
}
132+
133+
if('TEAMCITY_VERSION'inenv){
134+
return/^(9\.(0*[1-9]\d*)\.|\d{2,}\.)/.test(env.TEAMCITY_VERSION) ?
135+
COLORS_16 : COLORS_2;
136+
}
137+
138+
switch(env.TERM_PROGRAM){
139+
case'iTerm.app':
140+
if(!env.TERM_PROGRAM_VERSION||
141+
/^[0-2]\./.test(env.TERM_PROGRAM_VERSION)){
142+
returnCOLORS_256;
143+
}
144+
returnCOLORS_16m;
145+
case'HyperTerm':
146+
case'Hyper':
147+
case'MacTerm':
148+
returnCOLORS_16m;
149+
case'Apple_Terminal':
150+
returnCOLORS_256;
151+
}
152+
153+
if(env.TERM){
154+
if(/^xterm-256/.test(env.TERM))
155+
returnCOLORS_256;
156+
if(/^screen|^xterm|^vt100|color|ansi|cygwin|linux/i.test(env.TERM))
157+
returnCOLORS_16;
158+
}
159+
160+
if(env.COLORTERM)
161+
returnCOLORS_16;
162+
163+
returnCOLORS_2;
164+
};
93165

94166
WriteStream.prototype._refreshSize=function(){
95167
varoldCols=this.columns;

‎test/parallel/test-assert.js‎

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -763,10 +763,13 @@ common.expectsError(
763763
);
764764

765765
// Test error diffs
766+
constcolors=process.stdout.isTTY&&process.stdout.getColorDepth()>1;
766767
conststart='Input A expected to deepStrictEqual input B:';
767-
constactExp='\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m';
768-
constplus='\u001b[32m+\u001b[39m';
769-
constminus='\u001b[31m-\u001b[39m';
768+
constactExp=colors ?
769+
'\u001b[32m+ expected\u001b[39m \u001b[31m- actual\u001b[39m' :
770+
'+ expected - actual';
771+
constplus=colors ? '\u001b[32m+\u001b[39m' : '+';
772+
constminus=colors ? '\u001b[31m-\u001b[39m' : '-';
770773
letmessage=[
771774
start,
772775
`${actExp} ... Lines skipped`,
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
'use strict';
2+
3+
constcommon=require('../common');
4+
constassert=require('assert').strict;
5+
/* eslint-disable no-restricted-properties */
6+
const{ openSync }=require('fs');
7+
consttty=require('tty');
8+
9+
const{ WriteStream }=require('tty');
10+
11+
// Do our best to grab a tty fd.
12+
functiongetTTYfd(){
13+
constttyFd=[0,1,2,4,5].find(tty.isatty);
14+
if(ttyFd===undefined){
15+
try{
16+
returnopenSync('/dev/tty');
17+
}catch(e){
18+
// There aren't any tty fd's available to use.
19+
return-1;
20+
}
21+
}
22+
returnttyFd;
23+
}
24+
25+
constfd=getTTYfd();
26+
27+
// Give up if we did not find a tty
28+
if(fd===-1)
29+
common.skip();
30+
31+
constwriteStream=newWriteStream(fd);
32+
33+
letdepth=writeStream.getColorDepth();
34+
35+
assert.equal(typeofdepth,'number');
36+
assert(depth>=1&&depth<=24);
37+
38+
// If the terminal does not support colors, skip the rest
39+
if(depth===1)
40+
common.skip();
41+
42+
assert.notEqual(writeStream.getColorDepth({TERM: 'dumb'}),depth);
43+
44+
// Deactivate colors
45+
consttmp=process.env.NODE_DISABLE_COLORS;
46+
process.env.NODE_DISABLE_COLORS=1;
47+
48+
depth=writeStream.getColorDepth();
49+
50+
assert.equal(depth,1);
51+
52+
process.env.NODE_DISABLE_COLORS=tmp;

0 commit comments

Comments
(0)