Skip to content

Commit

Permalink
handle escape as exit + clearFirst featue for autocomplete (#280)
Browse files Browse the repository at this point in the history
  • Loading branch information
ui3o committed Oct 26, 2020
1 parent 9628ebe commit 53ab834
Show file tree
Hide file tree
Showing 13 changed files with 60 additions and 10 deletions.
20 changes: 18 additions & 2 deletions lib/elements/autocomplete.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const getIndex = (arr, valOrTitle) => {
* @param {String} [opts.style='default'] Render style
* @param {String} [opts.fallback] Fallback message - initial to default value
* @param {String} [opts.initial] Index of the default value
* @param {Boolean} [opts.clearFirst] The first ESCAPE keypress will clear the input
* @param {Stream} [opts.stdin] The Readable stream to listen to
* @param {Stream} [opts.stdout] The Writable stream to write readline data to
* @param {String} [opts.noMatches] The no matches found label
Expand All @@ -39,6 +40,7 @@ class AutocompletePrompt extends Prompt {
this.select = this.initial || opts.cursor || 0;
this.i18n = { noMatches: opts.noMatches || 'no matches found' };
this.fallback = opts.fallback || this.initial;
this.clearFirst = opts.clearFirst || false;
this.suggestions = [];
this.input = '';
this.limit = opts.limit || 10;
Expand Down Expand Up @@ -96,8 +98,22 @@ class AutocompletePrompt extends Prompt {
this.render();
}

exit() {
if (this.clearFirst && this.input.length > 0) {
this.reset();
} else {
this.done = this.exited = true;
this.aborted = false;
this.fire();
this.render();
this.out.write('\n');
this.close();
}
}

abort() {
this.done = this.aborted = true;
this.exited = false;
this.fire();
this.render();
this.out.write('\n');
Expand All @@ -106,7 +122,7 @@ class AutocompletePrompt extends Prompt {

submit() {
this.done = true;
this.aborted = false;
this.aborted = this.exited = false;
this.fire();
this.render();
this.out.write('\n');
Expand Down Expand Up @@ -222,7 +238,7 @@ class AutocompletePrompt extends Prompt {
let { startIndex, endIndex } = entriesToDisplay(this.select, this.choices.length, this.limit);

this.outputText = [
style.symbol(this.done, this.aborted),
style.symbol(this.done, this.aborted, this.exited),
color.bold(this.msg),
style.delimiter(this.completing),
this.done && this.suggestions[this.select]
Expand Down
4 changes: 4 additions & 0 deletions lib/elements/confirm.js
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ class ConfirmPrompt extends Prompt {
this.render();
}

exit() {
this.abort();
}

abort() {
this.done = this.aborted = true;
this.fire();
Expand Down
4 changes: 4 additions & 0 deletions lib/elements/date.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,10 @@ class DatePrompt extends Prompt {
this.render();
}

exit() {
this.abort();
}

abort() {
this.done = this.aborted = true;
this.error = false;
Expand Down
4 changes: 4 additions & 0 deletions lib/elements/multiselect.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,10 @@ class MultiselectPrompt extends Prompt {
return this.value.filter(v => v.selected);
}

exit() {
this.abort();
}

abort() {
this.done = this.aborted = true;
this.fire();
Expand Down
4 changes: 4 additions & 0 deletions lib/elements/number.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,10 @@ class NumberPrompt extends Prompt {
this.render();
}

exit() {
this.abort();
}

abort() {
let x = this.value;
this.value = x !== `` ? x : this.initial;
Expand Down
7 changes: 4 additions & 3 deletions lib/elements/prompt.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ class Prompt extends EventEmitter {
this.in = opts.stdin || process.stdin;
this.out = opts.stdout || process.stdout;
this.onRender = (opts.onRender || (() => void 0)).bind(this);
const rl = readline.createInterface(this.in);
const rl = readline.createInterface({ input:this.in, escapeCodeTimeout:50 });
readline.emitKeypressEvents(this.in, rl);

if (this.in.isTTY) this.in.setRawMode(true);
Expand All @@ -40,7 +40,7 @@ class Prompt extends EventEmitter {
this.in.removeListener('keypress', keypress);
if (this.in.isTTY) this.in.setRawMode(false);
rl.close();
this.emit(this.aborted ? 'abort' : 'submit', this.value);
this.emit(this.aborted ? 'abort' : this.exited ? 'exit' : 'submit', this.value);
this.closed = true;
};

Expand All @@ -50,7 +50,8 @@ class Prompt extends EventEmitter {
fire() {
this.emit('state', {
value: this.value,
aborted: !!this.aborted
aborted: !!this.aborted,
exited: !!this.exited
});
}

Expand Down
4 changes: 4 additions & 0 deletions lib/elements/select.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class SelectPrompt extends Prompt {
this.render();
}

exit() {
this.abort();
}

abort() {
this.done = this.aborted = true;
this.fire();
Expand Down
4 changes: 4 additions & 0 deletions lib/elements/text.js
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,10 @@ class TextPrompt extends Prompt {
this.render();
}

exit() {
this.abort();
}

abort() {
this.value = this.value || this.initial;
this.done = this.aborted = true;
Expand Down
4 changes: 4 additions & 0 deletions lib/elements/toggle.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class TogglePrompt extends Prompt {
this.render();
}

exit() {
this.abort();
}

abort() {
this.done = this.aborted = true;
this.fire();
Expand Down
3 changes: 3 additions & 0 deletions lib/prompts.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,10 @@ function toPrompt(type, args, opts={}) {
const p = new el[type](args);
const onAbort = opts.onAbort || noop;
const onSubmit = opts.onSubmit || noop;
const onExit = opts.onExit || noop;
p.on('state', args.onState || noop);
p.on('submit', x => res(onSubmit(x)));
p.on('exit', x => res(onExit(x)));
p.on('abort', x => rej(onAbort(x)));
});
}
Expand Down Expand Up @@ -190,6 +192,7 @@ const byTitle = (input, choices) => Promise.resolve(
* @param {number} [args.limit=10] Max number of results to show
* @param {string} [args.style="default"] Render style ('default', 'password', 'invisible')
* @param {String} [args.initial] Index of the default value
* @param {boolean} [opts.clearFirst] The first ESCAPE keypress will clear the input
* @param {String} [args.fallback] Fallback message - defaults to initial value
* @param {function} [args.onState] On state change callback
* @param {Stream} [args.stdin] The Readable stream to listen to
Expand Down
4 changes: 2 additions & 2 deletions lib/util/action.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';

module.exports = (key, isSelect) => {
if (key.meta) return;
if (key.meta && key.name !== 'escape') return;

if (key.ctrl) {
if (key.name === 'a') return 'first';
Expand All @@ -21,7 +21,7 @@ module.exports = (key, isSelect) => {
if (key.name === 'backspace') return 'delete';
if (key.name === 'delete') return 'deleteForward';
if (key.name === 'abort') return 'abort';
if (key.name === 'escape') return 'abort';
if (key.name === 'escape') return 'exit';
if (key.name === 'tab') return 'next';
if (key.name === 'pagedown') return 'nextPage';
if (key.name === 'pageup') return 'prevPage';
Expand Down
5 changes: 3 additions & 2 deletions lib/util/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ const render = type => styles[type] || styles.default;
const symbols = Object.freeze({
aborted: c.red(figures.cross),
done: c.green(figures.tick),
exited: c.yellow(figures.cross),
default: c.cyan('?')
});

const symbol = (done, aborted) =>
aborted ? symbols.aborted : done ? symbols.done : symbols.default;
const symbol = (done, aborted, exited) =>
aborted ? symbols.aborted : exited ? symbols.exited : done ? symbols.done : symbols.default;

// between the question and the user's input.
const delimiter = completing =>
Expand Down
3 changes: 2 additions & 1 deletion readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -799,9 +799,10 @@ You can overwrite how choices are being filtered by passing your own suggest fun
| limit | `number` | Max number of results to show. Defaults to `10` |
| style | `string` | Render style (`default`, `password`, `invisible`, `emoji`). Defaults to `'default'` |
| initial | `string \| number` | Default initial value |
| clearFirst | `boolean` | The first ESCAPE keypress will clear the input |
| fallback | `string` | Fallback message when no match is found. Defaults to `initial` value if provided |
| onRender | `function` | On render callback. Keyword `this` refers to the current prompt |
| onState | `function` | On state change callback. Function signature is an `object` with two properties: `value` and `aborted` |
| onState | `function` | On state change callback. Function signature is an `object` with three properties: `value`, `aborted` and `exited` |

Example on what a `suggest` function might look like:
```js
Expand Down

0 comments on commit 53ab834

Please sign in to comment.