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