• 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, true, ctx);
55  if (ctx.code !== undefined) {
56    throw new ERR_TTY_INIT_FAILED(ctx);
57  }
58
59  net.Socket.call(this, {
60    highWaterMark: 0,
61    readable: true,
62    writable: false,
63    handle: tty,
64    ...options
65  });
66
67  this.isRaw = false;
68  this.isTTY = true;
69}
70
71ObjectSetPrototypeOf(ReadStream.prototype, net.Socket.prototype);
72ObjectSetPrototypeOf(ReadStream, net.Socket);
73
74ReadStream.prototype.setRawMode = function(flag) {
75  flag = !!flag;
76  const err = this._handle.setRawMode(flag);
77  if (err) {
78    this.emit('error', errors.errnoException(err, 'setRawMode'));
79    return this;
80  }
81  this.isRaw = flag;
82  return this;
83};
84
85function WriteStream(fd) {
86  if (!(this instanceof WriteStream))
87    return new WriteStream(fd);
88  if (fd >> 0 !== fd || fd < 0)
89    throw new ERR_INVALID_FD(fd);
90
91  const ctx = {};
92  const tty = new TTY(fd, false, ctx);
93  if (ctx.code !== undefined) {
94    throw new ERR_TTY_INIT_FAILED(ctx);
95  }
96
97  net.Socket.call(this, {
98    handle: tty,
99    readable: false,
100    writable: true
101  });
102
103  // Prevents interleaved or dropped stdout/stderr output for terminals.
104  // As noted in the following reference, local TTYs tend to be quite fast and
105  // this behavior has become expected due historical functionality on OS X,
106  // even though it was originally intended to change in v1.0.2 (Libuv 1.2.1).
107  // Ref: https://github.com/nodejs/node/pull/1771#issuecomment-119351671
108  this._handle.setBlocking(true);
109
110  const winSize = new Array(2);
111  const err = this._handle.getWindowSize(winSize);
112  if (!err) {
113    this.columns = winSize[0];
114    this.rows = winSize[1];
115  }
116}
117
118ObjectSetPrototypeOf(WriteStream.prototype, net.Socket.prototype);
119ObjectSetPrototypeOf(WriteStream, net.Socket);
120
121WriteStream.prototype.isTTY = true;
122
123WriteStream.prototype.getColorDepth = getColorDepth;
124
125WriteStream.prototype.hasColors = hasColors;
126
127WriteStream.prototype._refreshSize = function() {
128  const oldCols = this.columns;
129  const oldRows = this.rows;
130  const winSize = new Array(2);
131  const err = this._handle.getWindowSize(winSize);
132  if (err) {
133    this.emit('error', errors.errnoException(err, 'getWindowSize'));
134    return;
135  }
136  const { 0: newCols, 1: newRows } = winSize;
137  if (oldCols !== newCols || oldRows !== newRows) {
138    this.columns = newCols;
139    this.rows = newRows;
140    this.emit('resize');
141  }
142};
143
144// Backwards-compat
145WriteStream.prototype.cursorTo = function(x, y, callback) {
146  if (readline === undefined) readline = require('readline');
147  return readline.cursorTo(this, x, y, callback);
148};
149WriteStream.prototype.moveCursor = function(dx, dy, callback) {
150  if (readline === undefined) readline = require('readline');
151  return readline.moveCursor(this, dx, dy, callback);
152};
153WriteStream.prototype.clearLine = function(dir, callback) {
154  if (readline === undefined) readline = require('readline');
155  return readline.clearLine(this, dir, callback);
156};
157WriteStream.prototype.clearScreenDown = function(callback) {
158  if (readline === undefined) readline = require('readline');
159  return readline.clearScreenDown(this, callback);
160};
161WriteStream.prototype.getWindowSize = function() {
162  return [this.columns, this.rows];
163};
164
165module.exports = { isatty, ReadStream, WriteStream };
166