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 (error: any) { 40 await logCmdError(error); 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