Skip to content

Commit b1f140b

Browse files
Yanis Bensonsindresorhus
andcommitted
Discard stdin by default (#112)
Co-authored-by: Sindre Sorhus <[email protected]>
1 parent 8bcde17 commit b1f140b

File tree

5 files changed

+88
-6
lines changed

5 files changed

+88
-6
lines changed

example.js

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,31 +3,42 @@ const chalk = require('chalk');
33
const Ora = require('.');
44

55
const spinner = new Ora({
6+
discardStdin: false,
7+
text: 'Loading unicorns, not discarding stdin',
8+
spinner: process.argv[2]
9+
});
10+
11+
const spinnerDiscardingStdin = new Ora({
612
text: 'Loading unicorns',
713
spinner: process.argv[2]
814
});
915

10-
spinner.start();
16+
spinnerDiscardingStdin.start();
17+
18+
setTimeout(() => {
19+
spinnerDiscardingStdin.succeed();
20+
spinner.start();
21+
}, 3000);
1122

1223
setTimeout(() => {
1324
spinner.color = 'yellow';
1425
spinner.text = `Loading ${chalk.red('rainbows')}`;
15-
}, 1000);
26+
}, 4000);
1627

1728
setTimeout(() => {
1829
spinner.color = 'green';
1930
spinner.indent = 2;
2031
spinner.text = 'Loading with indent';
21-
}, 2000);
32+
}, 5000);
2233

2334
setTimeout(() => {
2435
spinner.indent = 0;
2536
spinner.spinner = 'moon';
2637
spinner.text = 'Loading with different spinners';
27-
}, 3000);
38+
}, 6000);
2839

2940
setTimeout(() => {
3041
spinner.succeed();
31-
}, 4000);
42+
}, 7000);
3243

3344
// $ node example.js nameOfSpinner

index.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,13 @@ declare namespace ora {
8989
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.
9090
*/
9191
readonly isEnabled?: boolean;
92+
93+
/**
94+
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.
95+
96+
@default true
97+
*/
98+
readonly discardStdin?: boolean;
9299
}
93100

94101
interface PersistOptions {

index.js

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,10 @@ const isInteractive = require('is-interactive');
1010
const TEXT = Symbol('text');
1111
const PREFIX_TEXT = Symbol('prefixText');
1212

13+
const noop = () => {};
14+
15+
const ASCII_ETX_CODE = 0x03; // Ctrl+C emits this code
16+
1317
class Ora {
1418
constructor(options) {
1519
if (typeof options === 'string') {
@@ -21,7 +25,8 @@ class Ora {
2125
this.options = Object.assign({
2226
text: '',
2327
color: 'cyan',
24-
stream: process.stderr
28+
stream: process.stderr,
29+
discardStdin: true
2530
}, options);
2631

2732
this.spinner = this.options.spinner;
@@ -38,6 +43,7 @@ class Ora {
3843
this.prefixText = this.options.prefixText;
3944
this.linesToClear = 0;
4045
this.indent = this.options.indent;
46+
this.discardStdin = this.options.discardStdin;
4147
}
4248

4349
get indent() {
@@ -171,6 +177,10 @@ class Ora {
171177
cliCursor.hide(this.stream);
172178
}
173179

180+
if (this.discardStdin && process.stdin.isTTY) {
181+
this.startDiscardingStdin();
182+
}
183+
174184
this.render();
175185
this.id = setInterval(this.render.bind(this), this.interval);
176186

@@ -190,9 +200,55 @@ class Ora {
190200
cliCursor.show(this.stream);
191201
}
192202

203+
if (this.discardStdin && process.stdin.isTTY) {
204+
this.stopDiscardingStdin();
205+
}
206+
193207
return this;
194208
}
195209

210+
startDiscardingStdin() {
211+
const {stdin} = process;
212+
213+
this._stdinOldRawMode = stdin.isRaw;
214+
this._stdinOldEmit = stdin.emit;
215+
this._stdinOldEmitOwnProperty = Object.prototype.hasOwnProperty.call(stdin, 'emit');
216+
217+
stdin.setRawMode(true);
218+
stdin.on('data', noop);
219+
220+
const self = this;
221+
stdin.emit = function (event, data, ...args) {
222+
if (event === 'data' && data.includes(ASCII_ETX_CODE)) {
223+
process.emit('SIGINT');
224+
}
225+
226+
self._stdinOldEmit.apply(this, [event, data, ...args]);
227+
};
228+
}
229+
230+
stopDiscardingStdin() {
231+
if (this._stdinOldEmit !== undefined) {
232+
const {stdin} = process;
233+
stdin.setRawMode(this._stdinOldRawMode);
234+
stdin.removeListener('data', noop);
235+
236+
if (stdin.listenerCount('data') === 0) {
237+
stdin.pause();
238+
}
239+
240+
if (this._stdinOldEmitOwnProperty) {
241+
stdin.emit = this._stdinOldEmit;
242+
} else {
243+
delete stdin.emit;
244+
}
245+
246+
this._stdinOldRawMode = undefined;
247+
this._stdinOldEmit = undefined;
248+
this._stdinOldEmitOwnProperty = undefined;
249+
}
250+
}
251+
196252
succeed(text) {
197253
return this.stopAndPersist({symbol: logSymbols.success, text});
198254
}

index.test-d.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ ora({indent: 1});
1515
ora({interval: 80});
1616
ora({stream: new PassThroughStream()});
1717
ora({isEnabled: true});
18+
ora({discardStdin: true});
1819

1920
spinner.color = 'yellow';
2021
spinner.text = 'Loading rainbows';

readme.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,13 @@ Force enable/disable the spinner. If not specified, the spinner will be enabled
117117

118118
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.
119119

120+
##### discardStdin
121+
122+
Type: `boolean`<br>
123+
Default: `true`
124+
125+
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.
126+
120127
### Instance
121128

122129
#### .start(text?)

0 commit comments

Comments
 (0)