Skip to content

Commit

Permalink
fix(tasks): clear listeners and input stream on select finalization
Browse files Browse the repository at this point in the history
  • Loading branch information
rafamel committed Apr 18, 2021
1 parent d404dc0 commit 048ca7d
Show file tree
Hide file tree
Showing 2 changed files with 84 additions and 13 deletions.
61 changes: 61 additions & 0 deletions src/helpers/emitter-intercept.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import { Members, VariadicFn } from 'type-core';

export interface EmitterIntercept<T> {
emitter: T;
emit(event: string, ...args: any[]): void;
clear(): void;
}

export function emitterIntercept<T extends NodeJS.EventEmitter>(
emitter: NodeJS.EventEmitter
): EmitterIntercept<T> {
let members: Members<VariadicFn[]> = {};

const proxy = new Proxy(emitter, {
get(target, key, receiver) {
switch (key) {
case 'on':
case 'once':
case 'addListener': {
return function(
this: any,
event: string,
listener: VariadicFn,
...args: any[]
): any {
if (!members[event]) members[event] = [];
members[event].push(listener);
return Reflect.apply(target[key], this, [event, listener, ...args]);
};
}
default: {
return Reflect.get(target, key, receiver);
}
}
}
});

return {
emitter: proxy as T,
emit(event: string, ...args: any[]): void {
const arr = members[event];
if (!arr) return;

const current = emitter.listeners(event);
const listeners = arr.filter((fn) => current.includes(fn));
members[event] = listeners;

for (const listener of listeners) {
listener(event, ...args);
}
},
clear(): void {
for (const [event, arr] of Object.entries(members)) {
for (const listener of arr) {
emitter.removeListener(event, listener);
}
}
members = {};
}
};
}
36 changes: 23 additions & 13 deletions src/tasks/stdio/select.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import { Empty, Members, TypeGuard } from 'type-core';
import { shallow } from 'merge-strategies';
import { Transform } from 'stream';
import cliSelect from 'cli-select';
import { Task } from '../../definitions';
import { getBadge } from '../../helpers/badges';
import { addPrefix } from '../../helpers/prefix';
import { emitterIntercept } from '../../helpers/emitter-intercept';
import { isInteractive } from '../../utils/is-interactive';
import { isCancelled } from '../../utils/is-cancelled';
import { style } from '../../utils/style';
Expand All @@ -12,7 +15,6 @@ import { raises } from '../exception/raises';
import { create } from '../creation/create';
import { print } from './print';
import { log } from './log';
import { addPrefix } from '~/helpers/prefix';

export interface SelectOptions {
/**
Expand Down Expand Up @@ -76,9 +78,12 @@ export function select(
);
}

const stdin = ctx.stdio[0];
const stdout = ctx.stdio[1];
const stdin = new Transform().pipe(ctx.stdio[0], { end: false });
const intercept = emitterIntercept(stdin);

function cancel(): void {
stdin.emit('keypress', undefined, {
intercept.emit('keypress', {
sequence: '\u001b',
name: 'escape',
ctrl: false,
Expand Down Expand Up @@ -107,22 +112,27 @@ export function select(
ctx
),
indentation: 0,
outputStream: ctx.stdio[1],
inputStream: Object.create(stdin, {
outputStream: stdout,
inputStream: Object.create(intercept.emitter, {
setRawMode: {
value(...args: any[]) {
return (stdin as any).setRawMode
? (stdin as any).setRawMode.apply(this, args)
return (intercept.emitter as any).setRawMode
? (intercept.emitter as any).setRawMode.apply(this, args)
: () => undefined;
}
}
})
}).then(
// User selection
({ value }) => value,
// User cancellation
() => null
);
})
.then(
// User selection
({ value }) => value,
// User cancellation
() => null
)
.finally(() => {
intercept.clear();
stdin.end();
});

if (timeout) clearTimeout(timeout);
if (await isCancelled(ctx)) return;
Expand Down

0 comments on commit 048ca7d

Please sign in to comment.