• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright Joyent, Inc. and other Node contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the
5// "Software"), to deal in the Software without restriction, including
6// without limitation the rights to use, copy, modify, merge, publish,
7// distribute, sublicense, and/or sell copies of the Software, and to permit
8// persons to whom the Software is furnished to do so, subject to the
9// following conditions:
10//
11// The above copyright notice and this permission notice shall be included
12// in all copies or substantial portions of the Software.
13//
14// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
15// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN
17// NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
18// DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
19// OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE
20// USE OR OTHER DEALINGS IN THE SOFTWARE.
21
22'use strict';
23
24const {
25  Array,
26  NumberIsInteger,
27  ObjectSetPrototypeOf,
28} = primordials;
29
30const net = require('net');
31const { TTY, isTTY } = internalBinding('tty_wrap');
32const errors = require('internal/errors');
33const { ERR_INVALID_FD, ERR_TTY_INIT_FAILED } = errors.codes;
34const {
35  getColorDepth,
36  hasColors,
37} = require('internal/tty');
38
39// Lazy loaded for startup performance.
40let readline;
41
42function isatty(fd) {
43  return NumberIsInteger(fd) && fd >= 0 && fd <= 2147483647 &&
44         isTTY(fd);
45}
46
47function ReadStream(fd, options) {
48  if (!(this instanceof ReadStream))
49    return new ReadStream(fd, options);
50  if (fd >> 0 !== fd || fd < 0)
51    throw new ERR_INVALID_FD(fd);
52
53  const ctx = {};
54  const tty = new TTY(fd, ctx);
55  if (ctx.code !== undefined) {
56    throw new ERR_TTY_INIT_FAILED(ctx);
57  }
58
59  net.Socket.call(this, {
60    readableHighWaterMark: 0,
61    handle: tty,
62    manualStart: true,
63    ...options,
64  });
65
66  this.isRaw = false;
67  this.isTTY = true;
68}
69
70ObjectSetPrototypeOf(ReadStream.prototype, net.Socket.prototype);
71ObjectSetPrototypeOf(ReadStream, net.Socket);
72
73ReadStream.prototype.setRawMode = function(flag) {
74  flag = !!flag;
75  const err = this._handle?.setRawMode(flag);
76  if (err) {
77    this.emit('error', errors.errnoException(err, 'setRawMode'));
78    return this;
79  }
80  this.isRaw = flag;
81  return this;
82};
83
84function WriteStream(fd) {
85  if (!(this instanceof WriteStream))
86    return new WriteStream(fd);
87  if (fd >> 0 !== fd || fd < 0)
88    throw new ERR_INVALID_FD(fd);
89
90  const ctx = {};
91  const tty = new TTY(fd, ctx);
92  if (ctx.code !== undefined) {
93    throw new ERR_TTY_INIT_FAILED(ctx);
94  }
95
96  net.Socket.call(this, {
97    readableHighWaterMark: 0,
98    handle: tty,
99    manualStart: true,
100  });
101
102  // Prevents interleaved or dropped stdout/stderr output for terminals.
103  // As noted in the following reference, local TTYs tend to be quite fast and
104  // this behavior has become expected due historical functionality on OS X,
105  // even though it was originally intended to change in v1.0.2 (Libuv 1.2.1).
106  // Ref: https://github.com/nodejs/node/pull/1771#issuecomment-119351671
107  this._handle.setBlocking(true);
108
109  const winSize = new Array(2);
110  const err = this._handle.getWindowSize(winSize);
111  if (!err) {
112    this.columns = winSize[0];
113    this.rows = winSize[1];
114  }
115}
116
117ObjectSetPrototypeOf(WriteStream.prototype, net.Socket.prototype);
118ObjectSetPrototypeOf(WriteStream, net.Socket);
119
120WriteStream.prototype.isTTY = true;
121
122WriteStream.prototype.getColorDepth = getColorDepth;
123
124WriteStream.prototype.hasColors = hasColors;
125
126WriteStream.prototype._refreshSize = function() {
127  const oldCols = this.columns;
128  const oldRows = this.rows;
129  const winSize = new Array(2);
130  const err = this._handle.getWindowSize(winSize);
131  if (err) {
132    this.emit('error', errors.errnoException(err, 'getWindowSize'));
133    return;
134  }
135  const { 0: newCols, 1: newRows } = winSize;
136  if (oldCols !== newCols || oldRows !== newRows) {
137    this.columns = newCols;
138    this.rows = newRows;
139    this.emit('resize');
140  }
141};
142
143// Backwards-compat
144WriteStream.prototype.cursorTo = function(x, y, callback) {
145  if (readline === undefined) readline = require('readline');
146  return readline.cursorTo(this, x, y, callback);
147};
148WriteStream.prototype.moveCursor = function(dx, dy, callback) {
149  if (readline === undefined) readline = require('readline');
150  return readline.moveCursor(this, dx, dy, callback);
151};
152WriteStream.prototype.clearLine = function(dir, callback) {
153  if (readline === undefined) readline = require('readline');
154  return readline.clearLine(this, dir, callback);
155};
156WriteStream.prototype.clearScreenDown = function(callback) {
157  if (readline === undefined) readline = require('readline');
158  return readline.clearScreenDown(this, callback);
159};
160WriteStream.prototype.getWindowSize = function() {
161  return [this.columns, this.rows];
162};
163
164module.exports = { isatty, ReadStream, WriteStream };
165