keyscan

Simpler keyboard scanning for Node

Usage no npm install needed!

<script type="module">
  import keyscan from 'https://cdn.skypack.dev/keyscan';
</script>

README

keyscan

Simple keyboard scanning for node

require('keyscan').make_scanner( (ch) => console.log('Caught ' + ch.parsed) );

Aren't there a thousand of these already?

Yep. And none of them are what I wanted.

  • One-liner node keypress handling
  • No need to configure beyond saying "this is the handler"
  • Suppress echo by default
  • Configurable (and removable) kill key(s)
  • Special keys (eg arrow keys) interpreted down to common sense strings
  • readline various-field defect repaired

Basic usage

Provide a function to keyscan. That function will be called whenever a non-control keyboard input is detected, with an object describing the input.

The object passed back to your handler function is a single depth flat object with six fields - the five mimicing what is exposed by the raw mode readline interface, plus a new field named parsed.

Generally you should use parsed, which is just (.name || .sequence). In most cases .parsed will contain .name, but there are characters (such as @) which have no .name. Checking for that and deferring to .sequence is a hassle, so, the .parsed property now reliably contains that behavior.

It's worth noting that .name frequently translates multiple keypresses to single keypresses (which is its purpose.) As a result, you may get keys represented in .parsed as strings like 'pagedown' and 'backspace'.

Example objects

This is the up arrow:

{
  "sequence": "\u001b[A",
  "name": "up",
  "ctrl": false,
  "meta": false,
  "shift": false,
  "code": "[A",
  "parsed": "up"
}

This is page down:

{
  "sequence": "\u001b[6~",
  "name": "pagedown",
  "ctrl": false,
  "meta": false,
  "shift": false,
  "code": "[6~",
  "parsed": "pagedown"
}

This is capital A produced with shift (note that only .sequence retains case)

{
  "sequence": "A",
  "name": "a",
  "ctrl": false,
  "meta": false,
  "shift": true,
  "parsed": "a"
}

This is capital A produced with caps lock (note that .shift is false here, unlike above)

{
  "sequence": "A",
  "name": "a",
  "ctrl": false,
  "meta": false,
  "shift": false,
  "parsed": "a"
}

This is ctrl+z

{
  "sequence": "\u001a",
  "name": "z",
  "ctrl": true,
  "meta": false,
  "shift": false,
  "parsed": "z"
}

Because of the integrated approach to control keys, you will not get events when people hit, say, shift or caps lock on their own. This is what most applications want and need, but it's not right for many action games, which may want to use control keys as weapon buttons.

On Windows and Linux platforms, meta is called alt. On Macintoshes, meta is called option.

Code examples are often more readable.

ES5 Example

For the most part, you'll want the .parsed property, which has the lower case character for letters, the symbol for symbols, and a string like 'down' for arrow keys.

If you're in an es5 environment:

console.log('Type ctrl+c to exit.\n-------------------\n');

var echo_ch = function(ks) { console.log('Caught ' + JSON.stringify(ks.parsed)); },

    keyscan = require('../dist/keyscan.js'),
    scanner = keyscan.make_scanner({ out: echo_ch });

Result

You'll see something like this:

$ node demo/es5_demo.js

Type ctrl+c to exit.
-------------------

Caught a
Caught b
Caught c
Caught 1
Caught 2
Caught %
Caught ^
Caught up
Caught right
Caught delete
Caught pagedown
Caught end
Caught home

Premades

There are also some convenience pre-made filters, such as

  • scanner.yn(opts) - for yes/no - pre-configured to filter for ['y','n']
    • scanner.ync(opts) - for yes/no/cancel - pre-configured to filter for ['y','n','c']
  • scanner.abc(opts) - for abc multiple choice - pre-configured to filter for ['a','b','c']
    • scanner.abcd(opts) - for abcd multiple choice - pre-configured to filter for ['a','b','c','d']
  • scanner.d09(opts) - for star ratings - pre-configured to filter for digits ['0','1','2','3','4','5','6','7','8','9']
    • scanner.d14(opts) - for star ratings - like .d09() but only accepts 1-4
    • scanner.d15(opts) - for star ratings - like .d14() but 1-5
    • scanner.d19(opts) - for 1-9 ratings - like .d09() but excludes zeroes
  • scanner.d09c(opts) - for digits - also accepts 'c', for cancellation
    • scanner.d14c(opts)
    • scanner.d15c(opts)
    • scanner.d19c(opts)

ES5 Full-Show Example

If you'd like to see the full keypress data instead, letting you see control modes and the raw character sequence, try this instead:

console.log('Type ctrl+c to exit.\n-------------------\n');

var echo_ch = function(ch) { console.log('Caught ' + JSON.stringify(ch)); },

    keyscan = require('../dist/keyscan.js'),
    scanner = keyscan.make_scanner({ out: echo_ch });

Result

You'll see something like this:

$ node demo/es5_demo.js

Type ctrl+c to exit.
-------------------

Caught {"sequence":"a","name":"a","ctrl":false,"meta":false,"shift":false,"parsed":"a"}
Caught {"sequence":"s","name":"s","ctrl":false,"meta":false,"shift":false,"parsed":"s"}
Caught {"sequence":"d","name":"d","ctrl":false,"meta":false,"shift":false,"parsed":"d"}
Caught {"sequence":"!","ctrl":false,"meta":false,"shift":false,"parsed":"!"}
Caught {"sequence":"@","ctrl":false,"meta":false,"shift":false,"parsed":"@"}
Caught {"sequence":"\u001b[A","name":"up","ctrl":false,"meta":false,"shift":false,"code":"[A","parsed":"up"}
Caught {"sequence":"\u001b[D","name":"left","ctrl":false,"meta":false,"shift":false,"code":"[D","parsed":"left"}
Caught {"sequence":"\u001b[C","name":"right","ctrl":false,"meta":false,"shift":false,"code":"[C","parsed":"right"}
Caught {"sequence":"\u001b[B","name":"down","ctrl":false,"meta":false,"shift":false,"code":"[B","parsed":"down"}
Caught {"sequence":"\u001b[6~","name":"pagedown","ctrl":false,"meta":false,"shift":false,"code":"[6~","parsed":"pagedown"}
Caught {"sequence":"\u001b[5~","name":"pageup","ctrl":false,"meta":false,"shift":false,"code":"[5~","parsed":"pageup"}
Caught {"sequence":"\u0003","name":"c","ctrl":true,"meta":false,"shift":false,"parsed":"c"}

ES6 Module Example

In an es6 environment with module support (ie, not node yet,)

console.log('Type ctrl+c to exit.\n-------------------\n');

import { make_scanner } from 'keyscan';

const keyscan = make_scanner({ out: function(ch) { console.log('Caught ' + JSON.stringify(ch)); } });

Result

You'll see something like this:

$ node demo/es5_demo.js

Type ctrl+c to exit.
-------------------

Caught {"sequence":"a","name":"a","ctrl":false,"meta":false,"shift":false,"parsed":"a"}

Other features

The key scanner offers other features as well.

Manually releasing the key scanner

If you want to release the scanning process without requiring the user to abort with ctrl+c, just .release the scanner.

console.log('\nType three things, then this will exit.\n---------------------------------------\n');

var count   = 0,

    keyscan = require('../dist/keyscan.js'),
    scanner,

    echo_3x = function(ch) {
        console.log('Caught ' + JSON.stringify(ch.parsed));
        ++count;
        if (count >= 3) {
            console.log('Three items reached.  Releasing.');
            scanner.release();
        }
    },

    scanner = keyscan.make_scanner({ out: echo_3x });

Result

You'll see something like this:

$ node demo/manual_release.js

Type three things, then this will exit.
---------------------------------------

Caught "1"
Caught "2"
Caught "3"
Three items reached.  Releasing.

Replacing the kill keys with isAbort

If you want to keep the termination character behavior, but want it to be something other than ctrl+c (or multiple things,) write a function, and pass it in as .isAbort:

console.log('\nType ctrl+x to exit.\n-------------------\n');

var echo_ch = function(ch) { console.log('Caught ' + JSON.stringify(ch.parsed)); },
    use_c_x = function(ch) { return (ch && (ch.ctrl) && (ch.name == 'x')); },

    keyscan = require('../dist/keyscan.js'),
    scanner = keyscan.make_scanner({ out: echo_ch, isAbort: use_c_x });

Todo:

  • read once only
  • unicode symbolic name representations
  • prompt (? may not make sense, deciding)
  • echo enable (wants unicode)
  • map keys to outputs (eg 's' echoes 'save')
  • make yn and ync show labels aftwerwards
  • .sync (inherently once)