Skip to content

Commit

Permalink
Fix discardStdin option (#135)
Browse files Browse the repository at this point in the history
  • Loading branch information
Yanis Benson authored and sindresorhus committed Nov 13, 2019
1 parent 974630f commit a4b2253
Show file tree
Hide file tree
Showing 5 changed files with 101 additions and 52 deletions.
8 changes: 8 additions & 0 deletions example.js
Expand Up @@ -15,6 +15,14 @@ const spinnerDiscardingStdin = new Ora({

spinnerDiscardingStdin.start();

setTimeout(() => {
spinnerDiscardingStdin.succeed();
}, 1000);

setTimeout(() => {
spinnerDiscardingStdin.start();
}, 2000);

setTimeout(() => {
spinnerDiscardingStdin.succeed();
spinner.start();
Expand Down
2 changes: 2 additions & 0 deletions index.d.ts
Expand Up @@ -94,6 +94,8 @@ declare namespace ora {

/**
Discard stdin input (except Ctrl+C) while running if it's TTY. This prevents the spinner from twitching on input, outputting broken lines on `Enter` key presses, and prevents buffering of input while the spinner is running.
This has no effect on Windows as there's no good way to implement discarding stdin properly there.
@default true
*/
Expand Down
140 changes: 88 additions & 52 deletions index.js
@@ -1,19 +1,99 @@
'use strict';
const readline = require('readline');
const chalk = require('chalk');
const cliCursor = require('cli-cursor');
const cliSpinners = require('cli-spinners');
const logSymbols = require('log-symbols');
const stripAnsi = require('strip-ansi');
const wcwidth = require('wcwidth');
const isInteractive = require('is-interactive');
const MuteStream = require('mute-stream');

const TEXT = Symbol('text');
const PREFIX_TEXT = Symbol('prefixText');

const noop = () => {};

const ASCII_ETX_CODE = 0x03; // Ctrl+C emits this code

class StdinDiscarder {
constructor() {
this.requests = 0;

this.mutedStream = new MuteStream();
this.mutedStream.pipe(process.stdout);
this.mutedStream.mute();

const self = this;
this.ourEmit = function (event, data, ...args) {
const {stdin} = process;
if (self.requests > 0 || stdin.emit === self.ourEmit) {
if (event === 'keypress') { // Fixes readline behavior
return;
}

if (event === 'data' && data.includes(ASCII_ETX_CODE)) {
process.emit('SIGINT');
}

self.oldEmit.apply(this, [event, data, ...args]);
} else {
process.stdin.emit.apply(this, [event, data, ...args]);
}
};
}

start() {
this.requests++;

if (this.requests === 1) {
this.realStart();
}
}

stop() {
if (this.requests <= 0) {
throw new Error('`stop` called more times than `start`');
}

this.requests--;

if (this.requests === 0) {
this.realStop();
}
}

realStart() {
// No known way to make it work reliably on Windows
if (process.platform === 'win32') {
return;
}

this.rl = readline.createInterface({
input: process.stdin,
output: this.mutedStream
});

this.rl.on('SIGINT', () => {
if (process.listenerCount('SIGINT') === 0) {
process.emit('SIGINT');
} else {
this.rl.close();
process.kill(process.pid, 'SIGINT');
}
});
}

realStop() {
if (process.platform === 'win32') {
return;
}

this.rl.close();
this.rl = undefined;
}
}

const stdinDiscarder = new StdinDiscarder();

class Ora {
constructor(options) {
if (typeof options === 'string') {
Expand Down Expand Up @@ -45,6 +125,7 @@ class Ora {
this.linesToClear = 0;
this.indent = this.options.indent;
this.discardStdin = this.options.discardStdin;
this.isDiscardingStdin = false;
}

get indent() {
Expand Down Expand Up @@ -183,7 +264,8 @@ class Ora {
}

if (this.discardStdin && process.stdin.isTTY) {
this.startDiscardingStdin();
this.isDiscardingStdin = true;
stdinDiscarder.start();
}

this.render();
Expand All @@ -205,60 +287,14 @@ class Ora {
cliCursor.show(this.stream);
}

if (this.discardStdin && process.stdin.isTTY) {
this.stopDiscardingStdin();
if (this.discardStdin && process.stdin.isTTY && this.isDiscardingStdin) {
stdinDiscarder.stop();
this.isDiscardingStdin = false;
}

return this;
}

startDiscardingStdin() {
const {stdin} = process;

this._stdinOldRawMode = stdin.isRaw;
this._stdinOldEmit = stdin.emit;
this._stdinOldEmitOwnProperty = Object.prototype.hasOwnProperty.call(stdin, 'emit');

stdin.setRawMode(true);
stdin.on('data', noop);
stdin.resume();

const self = this;
stdin.emit = function (event, data, ...args) {
if (event === 'keypress') { // Fixes readline behavior
return;
}

if (event === 'data' && data.includes(ASCII_ETX_CODE)) {
process.emit('SIGINT');
}

self._stdinOldEmit.apply(this, [event, data, ...args]);
};
}

stopDiscardingStdin() {
if (this._stdinOldEmit !== undefined) {
const {stdin} = process;
stdin.setRawMode(this._stdinOldRawMode);
stdin.removeListener('data', noop);

if (stdin.listenerCount('data') === 0) {
stdin.pause();
}

if (this._stdinOldEmitOwnProperty) {
stdin.emit = this._stdinOldEmit;
} else {
delete stdin.emit;
}

this._stdinOldRawMode = undefined;
this._stdinOldEmit = undefined;
this._stdinOldEmitOwnProperty = undefined;
}
}

succeed(text) {
return this.stopAndPersist({symbol: logSymbols.success, text});
}
Expand Down
1 change: 1 addition & 0 deletions package.json
Expand Up @@ -41,6 +41,7 @@
"cli-spinners": "^2.2.0",
"is-interactive": "^1.0.0",
"log-symbols": "^3.0.0",
"mute-stream": "0.0.8",
"strip-ansi": "^5.2.0",
"wcwidth": "^1.0.1"
},
Expand Down
2 changes: 2 additions & 0 deletions readme.md
Expand Up @@ -124,6 +124,8 @@ Default: `true`

Discard stdin input (except Ctrl+C) while running if it's TTY. This prevents the spinner from twitching on input, outputting broken lines on <kbd>Enter</kbd> key presses, and prevents buffering of input while the spinner is running.

This has no effect on Windows as there's no good way to implement discarding stdin properly there.

### Instance

#### .start(text?)
Expand Down

0 comments on commit a4b2253

Please sign in to comment.