Reputation: 6313
So I am hacking a bit with nodejs readline interface for practise and learning and I came across this weide behaviour.
What I am doing here is:
keypress
and call the same draw function (3.
) for the inside timestampThe idea is to only draw what is absolutely necessary and readline theoretically let's you do that.
It all works well and because I only listen to the arrow keys I use this custom stdout function to be able to ignore all other inputs.
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
(This is a reduced test script that works and exhibits my problem as well)
All keystrokes are ignored besides the arrow keys.
Now when I press the right
, top
or bottom
keys the inside of the frame is drawn and the cursor is returned to the bottom. As expected.
However when I press the left
key the frame is cleared and I find stdout prints the capital letter H
.
In fact when you press a bunch of other keys (that are ignored and produce no output) and then press the left
key you get all of them in one big chunk of output, replacing the H
letter.
I have no idea why... Repeated pressing the left
key will add more H
s. All other arrow keys work as expected.
(When removing customStdout
from the script I get the same behaviour for only the left
key.)
Please keep in mind I am not looking for a package that does that for me like bliss or charm. I am trying to learn and do it myself here
Upvotes: 2
Views: 1560
Reputation: 3063
I 've tried to debug the program a bit and it seems that it has something to do specifically with the left arrow key and possibly this is a bug of node. It seems that there is a buffer overflow or there is no null terminated character and the write function is emitted multiple times for the arrow key. You will understand this if you use a logger to write the results into a file (I've used 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();
}
});
The program has the proper behaviour if you press and keep the left alt and then start pressing the arrows buttons randomly. These two links will be helpful for you and maybe can give you more tips:
If you're a C or C++ programmer, you're no doubt aware that malloc() - the standard library function that returns allocated memory - does not initialize (fill with 0's) the memory that it allocates, by definition.
Turns out the same is true in Node.js; memory allocated for runtime usage in Node.js, as well as memory allocated for Buffer objects, is not initialized after being allocated.
https://nodesource.com/blog/nsolid-deepdive-into-security-policies-zero-fill-buffer-allocations/
There is this open bug about readline and emitKeypressEvents and it is about node v6.7.0 (the most possible is that this issue exists on previous versions as well)
https://github.com/nodejs/node/issues/8934
UPDATE
It seems that it was simpler than that (possibly the issue with the null-terminated character applies, though). The solution is the clear the the line using the clearLine of the readline api. This is a working solution for me:
'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
Upvotes: 2