Readline纠缠或如何捕捉关键事件,并在一个盒子里面画画

所以我正在使用nodejs readline接口进行实践和学习,我遇到了这个问题。

我在这里做的是:

  1. 创build一个readline接口
  2. 将光标移到顶部并绘制一个框架
  3. 将光标再次移动到顶部并在框架内打印一个时间戳(testing以查看我为每个事件重绘)
  4. 将光标移动到帧的末尾,所以当我退出时,我没有得到我的input在缓冲区的中间
  5. keypress并调用内部时间戳相同的绘制function( 3.

这个想法是只绘制绝对必要的,理论上来说,让你这样做。

这一切运作良好,因为我只听方向键我使用这个自定义标准输出function,可以忽略所有其他input。

 const Writable = require('stream').Writable; const Readline = require('readline'); //size of frame const MINWIDTH = 120; const MINHEIGHT = 40; //custom stdout to suppress output const customStdout = new Writable({ write: function(chunk, encoding, callback) { if( !this.muted ) { process.stdout.write( chunk, encoding ); } callback(); } }); //draw the frame only function drawFrame( RL ) { customStdout.muted = false; //stdout enabled Readline.cursorTo(RL, 0, 0); Readline.clearScreenDown(RL); RL.write(`╔${'═'.repeat( MINWIDTH - 2 )}╗\n`); RL.write(`║${' '.repeat( MINWIDTH - 2 )}║\n`.repeat( MINHEIGHT - 2 )); RL.write(`╚${'═'.repeat( MINWIDTH - 2 )}╝\n`); drawBoard( RL ); //now draw inside } //reset cursor and draw inside of frame function drawBoard( RL ) { customStdout.muted = false; //stdout enabled Readline.cursorTo(RL, 0, 2); //go to second line RL.write(`║ ${Date.now()}`); //print timestamp Readline.cursorTo(RL, 0, MINHEIGHT); //go to last nile customStdout.muted = true; //stdout disabled to ignore other input } //create the readline interface const RL = Readline.createInterface({ input: process.stdin, output: customStdout, terminal: true, historySize: 0, }); //some options I've been playing with Readline.emitKeypressEvents( process.stdin ); process.stdin.setEncoding('utf8'); if(process.stdin.isTTY) { process.stdin.setRawMode( true ); } //event handler for when key is pressed process.stdin.on("keypress", (chunk, key) => { if( key.name === 'right' || key.name === 'left' || key.name === 'up' || key.name === 'down' ) { drawBoard( RL ); //redraw board only } else { return; //do nothing } }); drawFrame( RL ); //now go off and draw frame 

(这是一个简化的testing脚本,工作和展示我的问题)

除箭头键外,所有击键都被忽略。 现在,当我按righttopbottom键时,画框内部被绘制,光标返回到底部。 正如所料。

在这里输入图像说明

但是,当我按left框架被清除,我发现标准输出打印大写字母H

在这里输入图像说明

事实上,当你按下一堆其他键(被忽略并且不产生输出),然后按下left你将它们全部放在一大块输出中,replaceH字母。 我不知道为什么…重复按left将增加更多的H s。 所有其他箭头键按预期工作。

(当从脚本中删除customStdout ,我只能得到与left相同的行为。)

请记住,我不是在寻找一个能让我喜欢幸福或魅力的包裹。 我正在努力学习,并在这里自己做

  • 任何人都可以解释为什么这是?
  • 我怎样才能避免这一点?
  • 我是否也需要总是画框?
  • 有没有另外一种方法来绘制整个屏幕?

我试图debugging一下程序,似乎它有一些特定的左箭头键,可能这是一个节点的错误。 看起来有一个缓冲区溢出,或没有空终止字符和箭头键多次写入function。 如果您使用logging器将结果写入文件(我使用bunyan),您将理解这一点:

 const bunyan = require('bunyan'); const log = bunyan.createLogger({ name: 'keys', streams: [{ level: 'info', path: 'keys.log' // log INFO and above to stdout }, { level: 'error', path: 'keys.log' // log ERROR and above to a file }] }); const customStdout = new Writable({ write: function (chunk, encoding, callback) { if (!this.muted) { log.info(chunk.toString('utf8')); process.stdout.write(chunk, encoding); } i++; return callback(); } }); 

如果您按下并保持左键,然后开始随机按下箭头button,则程序具有正确的行为。 这两个链接将有助于你,也许可以给你更多的提示:

如果您是C或C ++程序员,您无疑会意识到malloc()(返回分配的内存的标准库函数)不会根据定义初始化(填充0)所分配的内存。

原来在Node.js中也是如此; 为Node.js中的运行时使用分配的内存以及为Buffer对象分配的内存在分配之后未进行初始化。

https://nodesource.com/blog/nsolid-deepdive-into-security-policies-zero-fill-buffer-allocations/

有关于readline和emitKeypressEvents这个开放的bug,它是关于节点v6.7.0(最有可能的是,这个问题也存在于以前的版本)

https://github.com/nodejs/node/issues/8934

UPDATE

似乎它比这更简单(尽pipe可能与null终止字符的问题适用)。 解决方法是清除使用readline api的clearLine的行。 对我来说这是一个可行的解决scheme:

 'use strict'; const keypress = require('keypress'); const bunyan = require('bunyan'); const log = bunyan.createLogger({ name: 'keys', streams: [{ level: 'info', path: 'keys.log' // log INFO and above to stdout }, { level: 'error', path: 'keys.log' // log ERROR and above to a file }] }); const Writable = require('stream').Writable; const Readline = require('readline'); // size of frame const MINWIDTH = 120; const MINHEIGHT = 40; let keyName = null; let i = 0; // custom stdout to suppress output const customStdout = new Writable({ write: function (chunk, encoding, callback) { if (!this.muted) { log.info(keyName); log.info(Buffer.byteLength(chunk, 'utf8')); // log.info(chunk.toString('utf8')); process.stdout.write(chunk, encoding); } return callback(); } }); // draw the frame only function drawFrame(RL) { customStdout.muted = false; // stdout enabled Readline.cursorTo(RL, 0, 0); Readline.clearScreenDown(RL); RL.write(`╔${'═'.repeat( MINWIDTH - 2 )}╗\n`); RL.write(`║${' '.repeat( MINWIDTH - 2 )}║\n`.repeat(MINHEIGHT - 2)); RL.write(`╚${'═'.repeat( MINWIDTH - 2 )}╝\n`); drawBoard(RL); // now draw inside } // reset cursor and draw inside of frame function drawBoard(RL) { customStdout.muted = false; // stdout enabled Readline.cursorTo(RL, 0, 2); // go to second line RL.write(`║ ${Date.now()}`); // print timestamp Readline.cursorTo(RL, 0, MINHEIGHT); // go to last nile customStdout.muted = true; // stdout disabled to ignore other input } // create the readline interface const RL = Readline.createInterface({ input: process.stdin, output: customStdout, terminal: true, historySize: 0, }); // some options I've been playing with Readline.emitKeypressEvents(process.stdin); process.stdin.setEncoding('utf8'); process.stdin.setRawMode(true); keypress(process.stdin); RL.input.on("keypress", (chunk, key) => { console.log('keyname', key.name); RL.clearLine(); if ( key.name === 'right' || key.name === 'left' || key.name === 'up' || key.name === 'down' ) { drawBoard(RL); // redraw board only } else { return; // do nothing } }); drawFrame(RL); // now go off and draw frame