18d307f52SEvan Baconimport * as Log from '../../log'; 28d307f52SEvan Baconimport { logCmdError } from '../../utils/errors'; 38d307f52SEvan Bacon 411a5a4d2SEvan Baconconst CTRL_C = '\u0003'; 511a5a4d2SEvan Bacon 6*474a7a4bSEvan Baconconst debug = require('debug')('expo:start:interface:keyPressHandler') as typeof console.log; 7*474a7a4bSEvan Bacon 88d307f52SEvan Bacon/** An abstract key stroke interceptor. */ 98d307f52SEvan Baconexport class KeyPressHandler { 108d307f52SEvan Bacon private isInterceptingKeyStrokes = false; 118d307f52SEvan Bacon private isHandlingKeyPress = false; 128d307f52SEvan Bacon 138d307f52SEvan Bacon constructor(public onPress: (key: string) => Promise<any>) {} 148d307f52SEvan Bacon 158d307f52SEvan Bacon /** Start observing interaction pause listeners. */ 168d307f52SEvan Bacon createInteractionListener() { 178d307f52SEvan Bacon // Support observing prompts. 188d307f52SEvan Bacon let wasIntercepting = false; 198d307f52SEvan Bacon 208d307f52SEvan Bacon const listener = ({ pause }: { pause: boolean }) => { 218d307f52SEvan Bacon if (pause) { 228d307f52SEvan Bacon // Track if we were already intercepting key strokes before pausing, so we can 238d307f52SEvan Bacon // resume after pausing. 248d307f52SEvan Bacon wasIntercepting = this.isInterceptingKeyStrokes; 258d307f52SEvan Bacon this.stopInterceptingKeyStrokes(); 268d307f52SEvan Bacon } else if (wasIntercepting) { 278d307f52SEvan Bacon // Only start if we were previously intercepting. 288d307f52SEvan Bacon this.startInterceptingKeyStrokes(); 298d307f52SEvan Bacon } 308d307f52SEvan Bacon }; 318d307f52SEvan Bacon 328d307f52SEvan Bacon return listener; 338d307f52SEvan Bacon } 348d307f52SEvan Bacon 358d307f52SEvan Bacon private handleKeypress = async (key: string) => { 368d307f52SEvan Bacon // Prevent sending another event until the previous event has finished. 3711a5a4d2SEvan Bacon if (this.isHandlingKeyPress && key !== CTRL_C) { 388d307f52SEvan Bacon return; 398d307f52SEvan Bacon } 408d307f52SEvan Bacon this.isHandlingKeyPress = true; 418d307f52SEvan Bacon try { 42*474a7a4bSEvan Bacon debug(`Key pressed: ${key}`); 438d307f52SEvan Bacon await this.onPress(key); 4429975bfdSEvan Bacon } catch (error: any) { 4529975bfdSEvan Bacon await logCmdError(error); 468d307f52SEvan Bacon } finally { 478d307f52SEvan Bacon this.isHandlingKeyPress = false; 488d307f52SEvan Bacon } 498d307f52SEvan Bacon }; 508d307f52SEvan Bacon 518d307f52SEvan Bacon /** Start intercepting all key strokes and passing them to the input `onPress` method. */ 528d307f52SEvan Bacon startInterceptingKeyStrokes() { 538d307f52SEvan Bacon if (this.isInterceptingKeyStrokes) { 548d307f52SEvan Bacon return; 558d307f52SEvan Bacon } 568d307f52SEvan Bacon this.isInterceptingKeyStrokes = true; 578d307f52SEvan Bacon const { stdin } = process; 588d307f52SEvan Bacon // TODO: This might be here because of an old Node version. 598d307f52SEvan Bacon if (!stdin.setRawMode) { 608d307f52SEvan Bacon Log.warn('Using a non-interactive terminal, keyboard commands are disabled.'); 618d307f52SEvan Bacon return; 628d307f52SEvan Bacon } 638d307f52SEvan Bacon stdin.setRawMode(true); 648d307f52SEvan Bacon stdin.resume(); 658d307f52SEvan Bacon stdin.setEncoding('utf8'); 668d307f52SEvan Bacon stdin.on('data', this.handleKeypress); 678d307f52SEvan Bacon } 688d307f52SEvan Bacon 698d307f52SEvan Bacon /** Stop intercepting all key strokes. */ 708d307f52SEvan Bacon stopInterceptingKeyStrokes() { 718d307f52SEvan Bacon if (!this.isInterceptingKeyStrokes) { 728d307f52SEvan Bacon return; 738d307f52SEvan Bacon } 748d307f52SEvan Bacon this.isInterceptingKeyStrokes = false; 758d307f52SEvan Bacon const { stdin } = process; 768d307f52SEvan Bacon stdin.removeListener('data', this.handleKeypress); 778d307f52SEvan Bacon // TODO: This might be here because of an old Node version. 788d307f52SEvan Bacon if (!stdin.setRawMode) { 798d307f52SEvan Bacon Log.warn('Using a non-interactive terminal, keyboard commands are disabled.'); 808d307f52SEvan Bacon return; 818d307f52SEvan Bacon } 828d307f52SEvan Bacon stdin.setRawMode(false); 838d307f52SEvan Bacon stdin.resume(); 848d307f52SEvan Bacon } 858d307f52SEvan Bacon} 86