1import * as Log from '../../log';
2import { logCmdError } from '../../utils/errors';
3
4/** An abstract key stroke interceptor. */
5export class KeyPressHandler {
6  private isInterceptingKeyStrokes = false;
7  private isHandlingKeyPress = false;
8
9  constructor(public onPress: (key: string) => Promise<any>) {}
10
11  /** Start observing interaction pause listeners. */
12  createInteractionListener() {
13    // Support observing prompts.
14    let wasIntercepting = false;
15
16    const listener = ({ pause }: { pause: boolean }) => {
17      if (pause) {
18        // Track if we were already intercepting key strokes before pausing, so we can
19        // resume after pausing.
20        wasIntercepting = this.isInterceptingKeyStrokes;
21        this.stopInterceptingKeyStrokes();
22      } else if (wasIntercepting) {
23        // Only start if we were previously intercepting.
24        this.startInterceptingKeyStrokes();
25      }
26    };
27
28    return listener;
29  }
30
31  private handleKeypress = async (key: string) => {
32    // Prevent sending another event until the previous event has finished.
33    if (this.isHandlingKeyPress) {
34      return;
35    }
36    this.isHandlingKeyPress = true;
37    try {
38      await this.onPress(key);
39    } catch (err) {
40      await logCmdError(err);
41    } finally {
42      this.isHandlingKeyPress = false;
43    }
44  };
45
46  /** Start intercepting all key strokes and passing them to the input `onPress` method. */
47  startInterceptingKeyStrokes() {
48    if (this.isInterceptingKeyStrokes) {
49      return;
50    }
51    this.isInterceptingKeyStrokes = true;
52    const { stdin } = process;
53    // TODO: This might be here because of an old Node version.
54    if (!stdin.setRawMode) {
55      Log.warn('Using a non-interactive terminal, keyboard commands are disabled.');
56      return;
57    }
58    stdin.setRawMode(true);
59    stdin.resume();
60    stdin.setEncoding('utf8');
61    stdin.on('data', this.handleKeypress);
62  }
63
64  /** Stop intercepting all key strokes. */
65  stopInterceptingKeyStrokes() {
66    if (!this.isInterceptingKeyStrokes) {
67      return;
68    }
69    this.isInterceptingKeyStrokes = false;
70    const { stdin } = process;
71    stdin.removeListener('data', this.handleKeypress);
72    // TODO: This might be here because of an old Node version.
73    if (!stdin.setRawMode) {
74      Log.warn('Using a non-interactive terminal, keyboard commands are disabled.');
75      return;
76    }
77    stdin.setRawMode(false);
78    stdin.resume();
79  }
80}
81