From b1f140bbe2426fc841622258201b7b60476f1e3b Mon Sep 17 00:00:00 2001 From: Yanis Benson Date: Sun, 22 Sep 2019 11:44:04 +0300 Subject: [PATCH] Discard stdin by default (#112) Co-authored-by: Sindre Sorhus --- example.js | 21 +++++++++++++----- index.d.ts | 7 ++++++ index.js | 58 ++++++++++++++++++++++++++++++++++++++++++++++++- index.test-d.ts | 1 + readme.md | 7 ++++++ 5 files changed, 88 insertions(+), 6 deletions(-) diff --git a/example.js b/example.js index 2e6d824..1a6585d 100644 --- a/example.js +++ b/example.js @@ -3,31 +3,42 @@ const chalk = require('chalk'); const Ora = require('.'); const spinner = new Ora({ + discardStdin: false, + text: 'Loading unicorns, not discarding stdin', + spinner: process.argv[2] +}); + +const spinnerDiscardingStdin = new Ora({ text: 'Loading unicorns', spinner: process.argv[2] }); -spinner.start(); +spinnerDiscardingStdin.start(); + +setTimeout(() => { + spinnerDiscardingStdin.succeed(); + spinner.start(); +}, 3000); setTimeout(() => { spinner.color = 'yellow'; spinner.text = `Loading ${chalk.red('rainbows')}`; -}, 1000); +}, 4000); setTimeout(() => { spinner.color = 'green'; spinner.indent = 2; spinner.text = 'Loading with indent'; -}, 2000); +}, 5000); setTimeout(() => { spinner.indent = 0; spinner.spinner = 'moon'; spinner.text = 'Loading with different spinners'; -}, 3000); +}, 6000); setTimeout(() => { spinner.succeed(); -}, 4000); +}, 7000); // $ node example.js nameOfSpinner diff --git a/index.d.ts b/index.d.ts index d83216c..f173062 100644 --- a/index.d.ts +++ b/index.d.ts @@ -89,6 +89,13 @@ declare namespace ora { Note that `{isEnabled: false}` doesn't mean it won't output anything. It just means it won't output the spinner, colors, and other ansi escape codes. It will still log text. */ readonly isEnabled?: boolean; + + /** + 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. + + @default true + */ + readonly discardStdin?: boolean; } interface PersistOptions { diff --git a/index.js b/index.js index 73bf528..0b8191e 100644 --- a/index.js +++ b/index.js @@ -10,6 +10,10 @@ const isInteractive = require('is-interactive'); const TEXT = Symbol('text'); const PREFIX_TEXT = Symbol('prefixText'); +const noop = () => {}; + +const ASCII_ETX_CODE = 0x03; // Ctrl+C emits this code + class Ora { constructor(options) { if (typeof options === 'string') { @@ -21,7 +25,8 @@ class Ora { this.options = Object.assign({ text: '', color: 'cyan', - stream: process.stderr + stream: process.stderr, + discardStdin: true }, options); this.spinner = this.options.spinner; @@ -38,6 +43,7 @@ class Ora { this.prefixText = this.options.prefixText; this.linesToClear = 0; this.indent = this.options.indent; + this.discardStdin = this.options.discardStdin; } get indent() { @@ -171,6 +177,10 @@ class Ora { cliCursor.hide(this.stream); } + if (this.discardStdin && process.stdin.isTTY) { + this.startDiscardingStdin(); + } + this.render(); this.id = setInterval(this.render.bind(this), this.interval); @@ -190,9 +200,55 @@ class Ora { cliCursor.show(this.stream); } + if (this.discardStdin && process.stdin.isTTY) { + this.stopDiscardingStdin(); + } + 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); + + const self = this; + stdin.emit = function (event, data, ...args) { + 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}); } diff --git a/index.test-d.ts b/index.test-d.ts index b2caf9d..3ff2f99 100644 --- a/index.test-d.ts +++ b/index.test-d.ts @@ -15,6 +15,7 @@ ora({indent: 1}); ora({interval: 80}); ora({stream: new PassThroughStream()}); ora({isEnabled: true}); +ora({discardStdin: true}); spinner.color = 'yellow'; spinner.text = 'Loading rainbows'; diff --git a/readme.md b/readme.md index 2b70c43..c4e5754 100644 --- a/readme.md +++ b/readme.md @@ -117,6 +117,13 @@ Force enable/disable the spinner. If not specified, the spinner will be enabled Note that `{isEnabled: false}` doesn't mean it won't output anything. It just means it won't output the spinner, colors, and other ansi escape codes. It will still log text. +##### discardStdin + +Type: `boolean`
+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 Enter key presses, and prevents buffering of input while the spinner is running. + ### Instance #### .start(text?)